From ea58a2e5d977f4e43b93241680dc426f5a70a578 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 22 Mar 2024 13:14:26 +0000 Subject: [PATCH 001/292] Update dependencies from https://github.com/dotnet/arcade build Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24170.6 --- eng/Version.Details.xml | 8 ++++---- global.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 5c9864415aeaa..0bc2088df684a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - f311667e0587f19c3fa9553a909975662107a351 + 8e3e00a76f467cc262dc14f6466ab884b2c4eb96 @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - f311667e0587f19c3fa9553a909975662107a351 + 8e3e00a76f467cc262dc14f6466ab884b2c4eb96 https://github.com/dotnet/roslyn-analyzers diff --git a/global.json b/global.json index 59843f320d926..a6544a313da8d 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24165.4", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24165.4" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24170.6", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24170.6" } } From bba3f9b16df523154c00aebd953ef065fed3b742 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 23 Mar 2024 13:04:27 +0000 Subject: [PATCH 002/292] Update dependencies from https://github.com/dotnet/arcade build Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24170.6 From 1004fc0fd40bd31900901b0b0c66eb8ca29904e3 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sun, 24 Mar 2024 12:55:32 +0000 Subject: [PATCH 003/292] Update dependencies from https://github.com/dotnet/arcade build Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24170.6 From b27c081970d34a56937e544110931959ad10e005 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Mon, 25 Mar 2024 13:02:10 +0000 Subject: [PATCH 004/292] Update dependencies from https://github.com/dotnet/arcade build Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24172.5 --- eng/Version.Details.xml | 8 ++++---- eng/common/templates-official/job/job.yml | 2 +- global.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 0bc2088df684a..eefbb37e7aeef 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - 8e3e00a76f467cc262dc14f6466ab884b2c4eb96 + ceb071c1060b8e6de404c065b4045442570caa18 @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 8e3e00a76f467cc262dc14f6466ab884b2c4eb96 + ceb071c1060b8e6de404c065b4045442570caa18 https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml index a2709d10562ca..0604277a2ff52 100644 --- a/eng/common/templates-official/job/job.yml +++ b/eng/common/templates-official/job/job.yml @@ -128,7 +128,7 @@ jobs: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - ${{ if eq(parameters.enableMicrobuild, 'true') }}: - - task: MicroBuildSigningPlugin@3 + - task: MicroBuildSigningPlugin@4 displayName: Install MicroBuild plugin inputs: signType: $(_SignType) diff --git a/global.json b/global.json index a6544a313da8d..53537d5f1f61b 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24170.6", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24170.6" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24172.5", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24172.5" } } From c5c1507183f8c0db506f1de26342079dc4c19095 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Wed, 27 Mar 2024 13:39:17 +0000 Subject: [PATCH 005/292] Update dependencies from https://github.com/dotnet/arcade build 20240326.8 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24176.8 --- eng/Version.Details.xml | 8 ++++---- eng/common/templates-official/job/job.yml | 1 + global.json | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index eefbb37e7aeef..cf8de09b2e66c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - ceb071c1060b8e6de404c065b4045442570caa18 + 48e9e0d2164de0535446809364724da8962123a6 @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - ceb071c1060b8e6de404c065b4045442570caa18 + 48e9e0d2164de0535446809364724da8962123a6 https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml index 0604277a2ff52..1f035fee73f4a 100644 --- a/eng/common/templates-official/job/job.yml +++ b/eng/common/templates-official/job/job.yml @@ -136,6 +136,7 @@ jobs: feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json env: TeamName: $(_TeamName) + MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)' continueOnError: ${{ parameters.continueOnError }} condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) diff --git a/global.json b/global.json index 53537d5f1f61b..89aa5199fee2f 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24172.5", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24172.5" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24176.8", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24176.8" } } From 4fec197abc2346aa24208dd675b1a13200dfbe98 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Thu, 28 Mar 2024 13:28:11 +0000 Subject: [PATCH 006/292] Update dependencies from https://github.com/dotnet/arcade build 20240327.1 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24177.1 --- eng/Version.Details.xml | 8 ++++---- eng/common/native/init-compiler.sh | 2 +- global.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index cf8de09b2e66c..bf6583ca6fb11 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - 48e9e0d2164de0535446809364724da8962123a6 + b17b2a1bb7da23253043dee059f374b00f3e321a @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - 48e9e0d2164de0535446809364724da8962123a6 + b17b2a1bb7da23253043dee059f374b00f3e321a https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh index f5c1ec7eafeb2..2d5660642b8d4 100644 --- a/eng/common/native/init-compiler.sh +++ b/eng/common/native/init-compiler.sh @@ -63,7 +63,7 @@ if [ -z "$CLR_CC" ]; then # Set default versions if [ -z "$majorVersion" ]; then # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. - if [ "$compiler" = "clang" ]; then versions="17 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5" + if [ "$compiler" = "clang" ]; then versions="18 17 16 15 14 13 12 11 10 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5" elif [ "$compiler" = "gcc" ]; then versions="13 12 11 10 9 8 7 6 5 4.9"; fi for version in $versions; do diff --git a/global.json b/global.json index 89aa5199fee2f..1b809102d42df 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24176.8", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24176.8" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24177.1", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24177.1" } } From fdd0d445812dff39b068e2a30078721c23addabd Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 29 Mar 2024 13:13:20 +0000 Subject: [PATCH 007/292] Update dependencies from https://github.com/dotnet/arcade build 20240327.1 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24177.1 From c660b67b7a07a18b6a8825743d87abc5e8505f65 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 30 Mar 2024 13:22:12 +0000 Subject: [PATCH 008/292] Update dependencies from https://github.com/dotnet/arcade build 20240329.4 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24179.4 --- eng/Version.Details.xml | 8 ++++---- eng/common/templates-official/job/onelocbuild.yml | 2 +- .../templates-official/job/publish-build-assets.yml | 4 ++-- eng/common/templates-official/job/source-build.yml | 2 +- .../templates-official/job/source-index-stage1.yml | 2 +- .../templates-official/post-build/post-build.yml | 10 +++++----- .../templates-official/variables/pool-providers.yml | 2 +- global.json | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index bf6583ca6fb11..2b46d03cd1241 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - b17b2a1bb7da23253043dee059f374b00f3e321a + fc2b7849b25c4a21457feb6da5fc7c9806a80976 @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - b17b2a1bb7da23253043dee059f374b00f3e321a + fc2b7849b25c4a21457feb6da5fc7c9806a80976 https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/templates-official/job/onelocbuild.yml b/eng/common/templates-official/job/onelocbuild.yml index ba9ba49303292..52b4d05d3f8dd 100644 --- a/eng/common/templates-official/job/onelocbuild.yml +++ b/eng/common/templates-official/job/onelocbuild.yml @@ -56,7 +56,7 @@ jobs: # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: diff --git a/eng/common/templates-official/job/publish-build-assets.yml b/eng/common/templates-official/job/publish-build-assets.yml index 53138622fe7a3..589ac80a18b74 100644 --- a/eng/common/templates-official/job/publish-build-assets.yml +++ b/eng/common/templates-official/job/publish-build-assets.yml @@ -60,8 +60,8 @@ jobs: os: windows # If it's not devdiv, it's dnceng ${{ if ne(variables['System.TeamProject'], 'DevDiv') }}: - name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 os: windows steps: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: diff --git a/eng/common/templates-official/job/source-build.yml b/eng/common/templates-official/job/source-build.yml index 8aba3b44bb252..f193dfbe23668 100644 --- a/eng/common/templates-official/job/source-build.yml +++ b/eng/common/templates-official/job/source-build.yml @@ -52,7 +52,7 @@ jobs: ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $[replace(replace(eq(contains(coalesce(variables['System.PullRequest.TargetBranch'], variables['Build.SourceBranch'], 'refs/heads/main'), 'release'), 'true'), True, 'NetCore1ESPool-Svc-Internal'), False, 'NetCore1ESPool-Internal')] - image: 1es-mariner-2-pt + image: 1es-mariner-2 os: linux ${{ if ne(parameters.platform.pool, '') }}: diff --git a/eng/common/templates-official/job/source-index-stage1.yml b/eng/common/templates-official/job/source-index-stage1.yml index 4b63373917085..f0513aee5b0da 100644 --- a/eng/common/templates-official/job/source-index-stage1.yml +++ b/eng/common/templates-official/job/source-index-stage1.yml @@ -33,7 +33,7 @@ jobs: demands: ImageOverride -equals windows.vs2019.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: windows.vs2022.amd64 os: windows steps: diff --git a/eng/common/templates-official/post-build/post-build.yml b/eng/common/templates-official/post-build/post-build.yml index 5c98fe1c0f3a9..da1f40958b450 100644 --- a/eng/common/templates-official/post-build/post-build.yml +++ b/eng/common/templates-official/post-build/post-build.yml @@ -110,7 +110,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: @@ -150,7 +150,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: - template: setup-maestro-vars.yml @@ -208,7 +208,7 @@ stages: # If it's not devdiv, it's dnceng ${{ else }}: name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows steps: - template: setup-maestro-vars.yml @@ -261,8 +261,8 @@ stages: os: windows # If it's not devdiv, it's dnceng ${{ else }}: - name: $(DncEngInternalBuildPool) - image: 1es-windows-2022-pt + name: NetCore1ESPool-Publishing-Internal + image: windows.vs2019.amd64 os: windows steps: - template: setup-maestro-vars.yml diff --git a/eng/common/templates-official/variables/pool-providers.yml b/eng/common/templates-official/variables/pool-providers.yml index beab7d1bfba06..1f308b24efc43 100644 --- a/eng/common/templates-official/variables/pool-providers.yml +++ b/eng/common/templates-official/variables/pool-providers.yml @@ -23,7 +23,7 @@ # # pool: # name: $(DncEngInternalBuildPool) -# image: 1es-windows-2022-pt +# image: 1es-windows-2022 variables: # Coalesce the target and source branches so we know when a PR targets a release branch diff --git a/global.json b/global.json index 1b809102d42df..d1af99caa9c33 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24177.1", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24177.1" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24179.4", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24179.4" } } From 83cd1c3693f5e5e67b0fa7aa05160b958e4d4acf Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Wed, 3 Apr 2024 13:12:02 +0000 Subject: [PATCH 009/292] Update dependencies from https://github.com/dotnet/arcade build 20240329.4 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24179.4 From 6b0cf7cdf8fb6d4078362be7eb9493cf9dfa2302 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Thu, 4 Apr 2024 12:59:28 +0000 Subject: [PATCH 010/292] Update dependencies from https://github.com/dotnet/arcade build 20240329.4 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24179.4 From cbe81264f509a58a52304c0f0e1d84ab76a31525 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 5 Apr 2024 12:46:55 +0000 Subject: [PATCH 011/292] Update dependencies from https://github.com/dotnet/arcade build 20240329.4 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24179.4 From a8e7b0b683ac24b8690f1150d4abcacfc2d333a9 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 6 Apr 2024 13:22:26 +0000 Subject: [PATCH 012/292] Update dependencies from https://github.com/dotnet/arcade build 20240404.3 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24204.3 --- eng/Version.Details.xml | 8 ++++---- .../templates-official/steps/component-governance.yml | 2 +- eng/common/templates/steps/component-governance.yml | 2 +- global.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 2b46d03cd1241..07b1f7d437360 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ - + https://github.com/dotnet/arcade - fc2b7849b25c4a21457feb6da5fc7c9806a80976 + 188340e12c0a372b1681ad6a5e72c608021efdba @@ -127,9 +127,9 @@ https://github.com/dotnet/roslyn 5d10d428050c0d6afef30a072c4ae68776621877 - + https://github.com/dotnet/arcade - fc2b7849b25c4a21457feb6da5fc7c9806a80976 + 188340e12c0a372b1681ad6a5e72c608021efdba https://github.com/dotnet/roslyn-analyzers diff --git a/eng/common/templates-official/steps/component-governance.yml b/eng/common/templates-official/steps/component-governance.yml index 0ecec47b0c917..cbba0596709da 100644 --- a/eng/common/templates-official/steps/component-governance.yml +++ b/eng/common/templates-official/steps/component-governance.yml @@ -4,7 +4,7 @@ parameters: steps: - ${{ if eq(parameters.disableComponentGovernance, 'true') }}: - - script: "echo ##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" displayName: Set skipComponentGovernanceDetection variable - ${{ if ne(parameters.disableComponentGovernance, 'true') }}: - task: ComponentGovernanceComponentDetection@0 diff --git a/eng/common/templates/steps/component-governance.yml b/eng/common/templates/steps/component-governance.yml index 0ecec47b0c917..cbba0596709da 100644 --- a/eng/common/templates/steps/component-governance.yml +++ b/eng/common/templates/steps/component-governance.yml @@ -4,7 +4,7 @@ parameters: steps: - ${{ if eq(parameters.disableComponentGovernance, 'true') }}: - - script: "echo ##vso[task.setvariable variable=skipComponentGovernanceDetection]true" + - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true" displayName: Set skipComponentGovernanceDetection variable - ${{ if ne(parameters.disableComponentGovernance, 'true') }}: - task: ComponentGovernanceComponentDetection@0 diff --git a/global.json b/global.json index d1af99caa9c33..5aa5a50b34485 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "17.8.1-2" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24179.4", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24179.4" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24204.3", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.24204.3" } } From a3f49983006077ac60363f6d0171ad8f2f2711bb Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sun, 7 Apr 2024 13:09:21 +0000 Subject: [PATCH 013/292] Update dependencies from https://github.com/dotnet/arcade build 20240404.3 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24204.3 From b4d4e219b147577a1fabd9ea81338144b4d9b6da Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Mon, 8 Apr 2024 13:00:36 +0000 Subject: [PATCH 014/292] Update dependencies from https://github.com/dotnet/arcade build 20240404.3 Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Helix.Sdk From Version 8.0.0-beta.24165.4 -> To Version 8.0.0-beta.24204.3 From 683292d090089073a69c5ecfbafe347212ffc2c1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 18:32:48 -0700 Subject: [PATCH 015/292] Move to collection exprs --- .../IDEDiagnosticIdToOptionMappingHelper.cs | 2 +- ...moveUnnecessaryImportsDiagnosticAnalyzer.cs | 2 +- ...AutomaticLineEnderCommandHandler_Helpers.cs | 8 ++++---- .../UI/Dashboard/RenameDashboard.xaml.cs | 2 +- .../QuickInfo/ProjectionBufferContent.cs | 2 +- .../AbstractToggleBlockCommentBase.cs | 2 +- .../Core/Editor/TextEditApplication.cs | 2 +- .../Aggregator/SettingsAggregator.cs | 2 +- .../DataProvider/SettingsProviderBase.cs | 2 +- .../Core/InlineRename/InlineRenameSession.cs | 2 +- .../AsyncCompletion/CompletionSource.cs | 2 +- .../AlwaysActivateInProcLanguageClient.cs | 2 +- .../NavigationBar/NavigationBarController.cs | 4 ++-- .../Preview/AbstractPreviewFactoryService.cs | 4 ++-- .../Test/CodeFixes/ExtensionOrderingTests.cs | 4 ++-- .../Test/Emit/CompilationOutputsTests.cs | 2 +- .../Test/Structure/StructureTaggerTests.cs | 2 +- .../AsynchronousOperationListenerTests.cs | 2 +- .../Test/Workspaces/TextFactoryTests.cs | 5 ++++- .../NavigateTo/AbstractNavigateToTests.cs | 2 +- .../ExpressionCompiler/UsingDebugInfoTests.cs | 8 ++++---- .../ResultProvider/Helpers/ArrayBuilder.cs | 2 +- .../Core/Test/FunctionResolver/Process.cs | 2 +- .../Core/Test/FunctionResolver/Resolver.cs | 2 +- .../Test/ResultProvider/ReflectionUtilities.cs | 2 +- .../DeclarationNameCompletionProvider.cs | 2 +- .../CSharpConvertLinqQueryToForEachProvider.cs | 2 +- .../AbstractToMethodConverter.cs | 4 ++-- ...egularConstructorCodeRefactoringProvider.cs | 2 +- .../CSharpEditAndContinueAnalyzer.cs | 2 +- ...CSharpGenerateParameterizedMemberService.cs | 2 +- ...SharpDiagnosticAnalyzerQuickInfoProvider.cs | 12 ++++++------ .../Helpers/EditAndContinueValidation.cs | 2 +- .../EditAndContinue/TopLevelEditingTests.cs | 2 +- .../AbstractAddImportFeatureService.cs | 2 +- .../AddImport/SymbolReferenceFinder.cs | 5 ++--- .../Configuration/ConfigurationUpdater.cs | 4 ++-- .../AbstractSuppressionBatchFixAllProvider.cs | 2 +- ...SuppressionCodeFixProvider.PragmaHelpers.cs | 4 ++-- ...r.RemoveSuppressionCodeAction.BatchFixer.cs | 2 +- .../CodeLens/CodeLensFindReferenceProgress.cs | 2 +- .../CodeRefactorings/CodeRefactoringService.cs | 2 +- .../AbstractChangeNamespaceService.cs | 2 +- ...ncNamespaceCodeRefactoringProvider.State.cs | 2 +- .../Completion/CharacterSetModificationRule.cs | 2 +- .../CompletionService_GetCompletions.cs | 4 ++-- .../AbstractSymbolCompletionProvider.cs | 2 +- ...hodImportCompletionHelper.SymbolComputer.cs | 2 +- .../ExtensionMethodImportCompletionHelper.cs | 2 +- .../Completion/Providers/RecommendedKeyword.cs | 2 +- .../Debugging/AbstractBreakpointResolver.cs | 2 +- .../EditAndContinue/DebuggingSession.cs | 4 ++-- .../DebuggingSessionTelemetry.cs | 2 +- .../EditAndContinueDocumentAnalysesCache.cs | 2 +- .../EditAndContinueMethodDebugInfoReader.cs | 2 +- .../EditAndContinue/EditAndContinueService.cs | 2 +- .../Portable/EditAndContinue/EditSession.cs | 4 ++-- .../Core/Portable/EditAndContinue/TraceLog.cs | 2 +- .../Portable/ExtractMethod/MethodExtractor.cs | 2 +- ...dUsagesService.DefinitionTrackingContext.cs | 2 +- ...actFindUsagesService_FindImplementations.cs | 6 +++--- ...shCodeFromMembersCodeRefactoringProvider.cs | 2 +- ...izedMemberService.AbstractInvocationInfo.cs | 2 +- .../AbstractGenerateTypeService.Editor.cs | 10 +++++----- .../InheritanceMargin/InheritanceMarginItem.cs | 2 +- .../Portable/InlineHints/InlineHintHelpers.cs | 2 +- ...eMethodRefactoringProvider.InlineContext.cs | 2 +- .../AbstractStructuralTypeDisplayService.cs | 8 ++++---- .../MetadataAsSourceFileService.cs | 2 +- .../MetadataAsSourceGeneratedFileInfo.cs | 2 +- ...gateToSearchService.CachedDocumentSearch.cs | 2 +- ...eToSearchService.GeneratedDocumentSearch.cs | 2 +- ...ractNavigateToSearchService.NormalSearch.cs | 4 ++-- .../Portable/NavigateTo/NavigateToSearcher.cs | 2 +- .../AbstractPullMemberUpRefactoringProvider.cs | 2 +- .../Portable/PullMemberUp/MembersPuller.cs | 2 +- .../CommonSemanticQuickInfoProvider.cs | 4 ++-- .../Portable/QuickInfo/IndentationHelper.cs | 2 +- .../Shared/Extensions/DocumentExtensions.cs | 2 +- .../Extensions/ISymbolExtensions_Sorting.cs | 3 +-- .../AbstractSignatureHelpProvider.cs | 2 +- .../StackTraceExplorerService.cs | 2 +- .../UnusedReferencesRemover.cs | 5 ++--- .../ValueTrackingProgressCollector.cs | 2 +- .../FileWatching/LspFileChangeWatcher.cs | 2 +- .../FileWatching/SimpleFileChangeWatcher.cs | 2 +- .../LanguageServerProjectSystem.cs | 2 +- .../HostWorkspace/ProjectDependencyHelper.cs | 2 +- .../HostWorkspace/WorkspaceProject.cs | 6 +++--- .../Handler/Restore/RestoreHandler.cs | 2 +- .../Testing/TestDiscoverer.DiscoveryHandler.cs | 2 +- .../LanguageServerEndpointAttribute.cs | 2 +- .../Protocol/DefaultCapabilitiesProvider.cs | 2 +- .../Protocol/Extensions/ProtocolConversions.cs | 2 +- .../CodeFixService.ProjectCodeFixProvider.cs | 2 +- .../DecompiledSource/AssemblyResolver.cs | 2 +- ...nalyzer.InProcOrRemoteHostAnalyzerRunner.cs | 2 +- ...rementalAnalyzer.StateManager.HostStates.cs | 2 +- .../UnifiedSuggestedActionsSource.cs | 6 ++---- .../Handler/CodeActions/CodeActionHelpers.cs | 8 ++++---- .../Highlights/DocumentHighlightHandler.cs | 4 ++-- .../OnAutoInsert/OnAutoInsertHandler.cs | 2 +- .../GetTextDocumentWithContextHandler.cs | 2 +- .../Handler/References/FindUsagesLSPContext.cs | 4 ++-- .../SemanticTokens/SemanticTokensSchema.cs | 2 +- .../Protocol/LspServices/LspServices.cs | 2 +- .../Completion/CompletionFeaturesTests.cs | 8 ++++---- .../Symbols/DocumentSymbolsTests.cs | 2 +- ...ditAndContinueMethodDebugInfoReaderTests.cs | 2 +- .../ActiveStatementsDescription.cs | 4 ++-- .../EditAndContinue/SourceMarkers.cs | 2 +- .../Core/InteractiveHostPlatformInfo.cs | 2 +- .../Interactive/Core/RemoteExecutionResult.cs | 4 ++-- .../Core/RemoteInitializationResult.cs | 4 ++-- .../InteractiveSessionReferencesTests.cs | 4 ++-- src/Scripting/Core/Hosting/SynchronizedList.cs | 2 +- .../EditAndContinue/EditAndContinueTest.cs | 4 ++-- .../PdbUtilities/Reader/SymReaderFactory.cs | 2 +- .../Reader/Token2SourceLineExporter.cs | 2 +- src/Tools/BuildActionTelemetryTable/Program.cs | 5 ++--- src/Tools/BuildValidator/CompilationDiff.cs | 8 ++++---- src/Tools/BuildValidator/Program.cs | 4 ++-- .../Razor/RazorSpanMappingServiceWrapper.cs | 2 +- src/Tools/Replay/Replay.cs | 2 +- .../GenerateFilteredReferenceAssembliesTask.cs | 8 ++++---- src/Tools/Source/RunTests/AssemblyScheduler.cs | 4 ++-- src/Tools/Source/RunTests/Program.cs | 2 +- src/Tools/Source/RunTests/TestRunner.cs | 2 +- ...rpChangeSignatureViewModelFactoryService.cs | 2 +- .../SettingsEditorControl.xaml.cs | 2 +- .../VisualStudioErrorReportingService.cs | 2 +- .../Implementation/VsRefactorNotifyService.cs | 4 ++-- .../Def/Progression/GraphNodeIdCreation.cs | 18 +++++++++--------- .../Dialog/UnusedReferencesTableProvider.cs | 2 +- .../Impl/CodeModel/FileCodeModel_CodeGen.cs | 2 +- .../Options/AbstractOptionPreviewViewModel.cs | 4 ++-- .../NamingStyleOptionPageControl.xaml.cs | 4 ++-- .../Services/ServiceHubServicesTests.cs | 2 +- .../InProcess/EditorInProcess.cs | 2 +- .../InProcess/ScreenshotInProcess.cs | 4 ++-- .../Impl/Client/LanguageServiceUtils.cs | 2 +- .../Panels/WorkspacePanel.xaml.cs | 2 +- .../Definitions/GoToDefinitionHandler.cs | 6 +++--- .../CSharpSyntaxFormattingService.cs | 15 +++++++++------ ...onService.NodesAndTokensToReduceComputer.cs | 2 +- .../Build/ProjectBuildManager.cs | 2 +- .../MSBuild/ProjectFile/Extensions.cs | 2 +- .../MSBuild/ProjectFile/ProjectFile.cs | 4 ++-- .../MSBuild/MSBuild/BuildHostProcessManager.cs | 2 +- .../Core/MSBuild/MSBuild/MSBuildWorkspace.cs | 2 +- .../AbstractCaseCorrectionService.cs | 2 +- .../Classification/ClassifierHelper.cs | 2 +- .../CodeCleanup/AbstractCodeCleanerService.cs | 4 ++-- .../DiagnosticAnalysisResultBuilder.cs | 4 ++-- .../Core/Portable/Diagnostics/Extensions.cs | 2 +- .../Portable/Editing/ImportAdderService.cs | 2 +- .../FindReferences/DependentProjectsFinder.cs | 6 +++--- .../FindReferences/DependentTypeFinder.cs | 4 ++-- ...encesSearchEngine.BidirectionalSymbolSet.cs | 2 +- ...rencesSearchEngine.NonCascadingSymbolSet.cs | 2 +- ...ncesSearchEngine.UnidirectionalSymbolSet.cs | 2 +- .../Finders/AbstractReferenceFinder.cs | 2 +- .../FindSymbols/StreamingProgressCollector.cs | 2 +- ...ymbolFinder.FindReferencesServerCallback.cs | 2 +- .../Core/Portable/FindSymbols/SymbolFinder.cs | 2 +- .../LinkedFileDiffMergingSession.cs | 2 +- .../Core/Portable/Log/AggregateLogger.cs | 6 +++--- .../ConflictEngine/ConflictResolver.Session.cs | 11 +++++++---- .../ConflictEngine/RenamedSpansTracker.cs | 4 ++-- src/Workspaces/Core/Portable/Rename/Renamer.cs | 4 ++-- .../Shared/Extensions/ISymbolExtensions.cs | 2 +- ...ypeSymbolExtensions.AnonymousTypeRemover.cs | 2 +- ...tensions.UnavailableTypeParameterRemover.cs | 2 +- ...SymbolExtensions.UnnamedErrorTypeRemover.cs | 2 +- .../TestHooks/AsynchronousOperationListener.cs | 2 +- .../AsynchronousOperationListenerProvider.cs | 2 +- .../IPersistentStorageConfiguration.cs | 2 +- ...SystemProject.BatchingDocumentCollection.cs | 2 +- .../Solution/ProjectDependencyGraph.cs | 8 ++++---- ...ojectDependencyGraph_AddProjectReference.cs | 2 +- .../Solution/SolutionCompilationState.cs | 4 ++-- .../Workspace/Solution/SolutionState.cs | 2 +- .../CoreTest/BatchFixAllProviderTests.cs | 2 +- .../EditorConfigFileParserTests.cs | 2 +- ...eclarationsTests.TestSolutionsAndProject.cs | 2 +- .../CoreTest/SolutionTests/SolutionTests.cs | 2 +- .../SolutionTests/TryApplyChangesTests.cs | 2 +- .../CoreTestUtilities/MEF/TestComposition.cs | 6 +++--- .../Workspaces/TestWorkspace_XmlConsumption.cs | 10 +++++----- .../Workspaces/TestWorkspace`1.cs | 2 +- ...moteAsynchronousOperationListenerService.cs | 4 ++-- .../RemoteSemanticClassificationService.cs | 2 +- .../Indentation/CSharpSmartTokenFormatter.cs | 3 +-- .../Extensions/INamedTypeSymbolExtensions.cs | 4 ++-- .../SymbolUsageAnalysis.AnalysisData.cs | 2 +- ...s.DataFlowAnalyzer.FlowGraphAnalysisData.cs | 4 ++-- .../FileBannerFacts/AbstractFileBannerFacts.cs | 2 +- .../Core/Utilities/PathMetadataUtilities.cs | 2 +- .../RestrictedInternalsVisibleToAttribute.cs | 2 +- .../CSharpCodeGenerationService.cs | 4 ++-- .../CodeGeneration/CodeGenerationHelpers.cs | 4 ++-- .../CodeGenerationAbstractMethodSymbol.cs | 2 +- .../CodeGenerationAbstractNamedTypeSymbol.cs | 2 +- .../Core/Helpers/MefHostServicesHelpers.cs | 2 +- 204 files changed, 318 insertions(+), 316 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs index 7c3a5d350984d..b657d4cd0f720 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIdToOptionMappingHelper.cs @@ -69,7 +69,7 @@ public static void AddOptionMapping(string diagnosticId, ImmutableHashSet new ConcurrentDictionary>()); - AddOptionMapping(map, diagnosticId, languageGroup.ToImmutableHashSet()); + AddOptionMapping(map, diagnosticId, [.. languageGroup]); } } } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs index 690ac016a84b8..09655f4fec19a 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnnecessaryImports/AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.cs @@ -40,7 +40,7 @@ internal abstract class AbstractRemoveUnnecessaryImportsDiagnosticAnalyzer.Empty.Add(statement), + nodesToInsert: [statement], formattingOptions, cancellationToken), DoStatementSyntax doStatementNode => AddBraceToDoStatement(services, root, doStatementNode, formattingOptions, statement, cancellationToken), @@ -195,7 +195,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToDoStatement oldNode: doStatementNode, newNode: AddBlockToEmbeddedStatementOwner(doStatementNode, formattingOptions), anchorNode: doStatementNode, - nodesToInsert: ImmutableArray.Empty.Add(innerStatement), + nodesToInsert: [innerStatement], formattingOptions, cancellationToken); } @@ -251,7 +251,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToIfStatement ifStatementNode, AddBlockToEmbeddedStatementOwner(ifStatementNode, formattingOptions), ifStatementNode, - ImmutableArray.Empty.Add(innerStatement), + [innerStatement], formattingOptions, cancellationToken); } @@ -319,7 +319,7 @@ private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToElseClause( elseClauseNode, WithBraces(elseClauseNode, formattingOptions), elseClauseNode.Parent!, - ImmutableArray.Empty.Add(innerStatement), + [innerStatement], formattingOptions, cancellationToken); } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboard.xaml.cs b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboard.xaml.cs index 5cd92953a1c02..482304aa00701 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboard.xaml.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/UI/Dashboard/RenameDashboard.xaml.cs @@ -52,7 +52,7 @@ public RenameDashboard( _model = model; InitializeComponent(); - _tabNavigableChildren = new UIElement[] { this.OverloadsCheckbox, this.CommentsCheckbox, this.StringsCheckbox, this.FileRenameCheckbox, this.PreviewChangesCheckbox, this.ApplyButton, this.CloseButton }.ToList(); + _tabNavigableChildren = [this.OverloadsCheckbox, this.CommentsCheckbox, this.StringsCheckbox, this.FileRenameCheckbox, this.PreviewChangesCheckbox, this.ApplyButton, this.CloseButton]; _textView = textView; this.DataContext = model; diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs index a0bed1d52547c..66d9d95cff3a8 100644 --- a/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/ProjectionBufferContent.cs @@ -96,7 +96,7 @@ private IWpfTextView CreateView(ITextBuffer buffer) private IProjectionBuffer CreateBuffer() { return _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( - _editorOptionsService.Factory.GlobalOptions, _contentType, _spans.ToArray()); + _editorOptionsService.Factory.GlobalOptions, _contentType, [.. _spans]); } } } diff --git a/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs b/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs index c8c5859d075d5..f025bbca1dd3d 100644 --- a/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs +++ b/src/EditorFeatures/Core/CommentSelection/AbstractToggleBlockCommentBase.cs @@ -436,7 +436,7 @@ private ImmutableArray GetUncommentedSpansInSelection() } } - return uncommentedSpans.ToImmutableArray(); + return [.. uncommentedSpans]; } } } diff --git a/src/EditorFeatures/Core/Editor/TextEditApplication.cs b/src/EditorFeatures/Core/Editor/TextEditApplication.cs index c4bf5575e8f83..c0acaa4266745 100644 --- a/src/EditorFeatures/Core/Editor/TextEditApplication.cs +++ b/src/EditorFeatures/Core/Editor/TextEditApplication.cs @@ -17,7 +17,7 @@ internal static void UpdateText(SourceText newText, ITextBuffer buffer, EditOpti var oldSnapshot = buffer.CurrentSnapshot; var oldText = oldSnapshot.AsText(); var changes = newText.GetTextChanges(oldText); - UpdateText(changes.ToImmutableArray(), buffer, oldSnapshot, oldText, options); + UpdateText([.. changes], buffer, oldSnapshot, oldText, options); } public static void UpdateText(ImmutableArray textChanges, ITextBuffer buffer, EditOptions options) diff --git a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs index ead1708f6bec9..0395dfb45c7fc 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/Aggregator/SettingsAggregator.cs @@ -93,7 +93,7 @@ private static ISettingsProviderFactory GetOptionsProviderFactory(Workspac TryAddProviderForLanguage(LanguageNames.VisualBasic, workspace, providers); } - return new CombinedOptionsProviderFactory(providers.ToImmutableArray()); + return new CombinedOptionsProviderFactory([.. providers]); static void TryAddProviderForLanguage(string language, Workspace workspace, List> providers) { diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs index fb310e0fc78f7..b1a04b9621a9f 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs @@ -88,7 +88,7 @@ public ImmutableArray GetCurrentDataSnapshot() { lock (s_gate) { - return _snapshot.ToImmutableArray(); + return [.. _snapshot]; } } diff --git a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs index fe13da5eaac87..4f30186d98c73 100644 --- a/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/InlineRename/InlineRenameSession.cs @@ -319,7 +319,7 @@ private void UpdateReferenceLocationsTask() // https://github.com/dotnet/roslyn/issues/40890 await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - RaiseSessionSpansUpdated(inlineRenameLocations.Locations.ToImmutableArray()); + RaiseSessionSpansUpdated([.. inlineRenameLocations.Locations]); return inlineRenameLocations; }); diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs index 2f738faae61df..df4f024f6bedc 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CompletionSource.cs @@ -82,7 +82,7 @@ internal CompletionSource( _asyncListener = asyncListener; _editorOptionsService = editorOptionsService; _isDebuggerTextView = textView is IDebuggerTextView; - _roles = textView.Roles.ToImmutableHashSet(); + _roles = [.. textView.Roles]; } public AsyncCompletionData.CompletionStartData InitializeCompletion( diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index fafe7b3331b95..0bdb6a8b06e80 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -117,7 +117,7 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa Range = true, Legend = new SemanticTokensLegend { - TokenTypes = SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes.ToArray(), + TokenTypes = [.. SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes], TokenModifiers = SemanticTokensSchema.TokenModifiers } }; diff --git a/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs index abce7a9ad1c88..385423e295bc6 100644 --- a/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/NavigationBar/NavigationBarController.cs @@ -215,13 +215,13 @@ private void GetProjectItems(out ImmutableArray projec return; } - projectItems = documents.Select(d => + projectItems = [.. documents.Select(d => new NavigationBarProjectItem( d.Project.Name, d.Project.GetGlyph(), workspace: d.Project.Solution.Workspace, documentId: d.Id, - language: d.Project.Language)).OrderBy(projectItem => projectItem.Text).ToImmutableArray(); + language: d.Project.Language)).OrderBy(projectItem => projectItem.Text)]; var document = _subjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext(); selectedProjectItem = document != null diff --git a/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs b/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs index 75a264060fcc5..fae0c03b08895 100644 --- a/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs +++ b/src/EditorFeatures/Core/Preview/AbstractPreviewFactoryService.cs @@ -589,7 +589,7 @@ public Task> CreateRemovedAnalyzerCo oldBuffer.CurrentSnapshot, "...", description, - originalSpans.ToArray()); + [.. originalSpans]); var changedBuffer = _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( _contentTypeRegistryService, @@ -597,7 +597,7 @@ public Task> CreateRemovedAnalyzerCo newBuffer.CurrentSnapshot, "...", description, - changedSpans.ToArray()); + [.. changedSpans]); #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) return await CreateNewDifferenceViewerAsync(leftWorkspace, rightWorkspace, originalBuffer, changedBuffer, zoomLevel, cancellationToken); diff --git a/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs b/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs index 687c123b63ba3..8541cd06e00ff 100644 --- a/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/ExtensionOrderingTests.cs @@ -48,7 +48,7 @@ public void TestNoCyclesInFixProviders() var vbProviders = providersPerLanguage[LanguageNames.VisualBasic]; ExtensionOrderer.TestAccessor.CheckForCycles(vbProviders); - actualOrder = ExtensionOrderer.Order(vbProviders).ToArray(); + actualOrder = [.. ExtensionOrderer.Order(vbProviders)]; Assert.True(actualOrder.Length > 0); Assert.True(actualOrder.IndexOf(p => p.Metadata.Name == PredefinedCodeFixProviderNames.AddImport) < actualOrder.IndexOf(p => p.Metadata.Name == PredefinedCodeFixProviderNames.FullyQualify)); @@ -106,7 +106,7 @@ public void TestNoCyclesInRefactoringProviders() var vbProviders = providersPerLanguage[LanguageNames.VisualBasic]; ExtensionOrderer.TestAccessor.CheckForCycles(vbProviders); - actualOrder = ExtensionOrderer.Order(vbProviders).ToArray(); + actualOrder = [.. ExtensionOrderer.Order(vbProviders)]; Assert.True(actualOrder.Length > 0); } diff --git a/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs b/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs index ca2b47721d8d9..434fcf3f2cd69 100644 --- a/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs +++ b/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs @@ -61,7 +61,7 @@ public void AssemblyAndPdb(DebugInformationFormat format) Stream? currentPdbStream = null; var outputs = new TestCompilationOutputs( - openAssemblyStream: () => currentPEStream = new MemoryStream(peImage.ToArray()), + openAssemblyStream: () => currentPEStream = new MemoryStream([.. peImage]), openPdbStream: () => { if (pdbStream == null) diff --git a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs index 793df59acf65f..00ceef681f06d 100644 --- a/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs +++ b/src/EditorFeatures/Test/Structure/StructureTaggerTests.cs @@ -339,7 +339,7 @@ private static async Task> GetTagsFromWorkspaceAsyn var context = new TaggerContext(document, view.TextSnapshot, frozenPartialSemantics: false); await provider.GetTestAccessor().ProduceTagsAsync(context); - return context.TagSpans.Select(x => x.Tag).OrderBy(t => t.OutliningSpan.Value.Start).ToList(); + return [.. context.TagSpans.Select(x => x.Tag).OrderBy(t => t.OutliningSpan.Value.Start)]; } #pragma warning restore CS0618 // Type or member is obsolete diff --git a/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs b/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs index 09da0aa6cb580..c364f5a0ffd40 100644 --- a/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs +++ b/src/EditorFeatures/Test/Utilities/AsynchronousOperationListenerTests.cs @@ -35,7 +35,7 @@ public void Dispose() lock (_tasks) { _tokenSource.Cancel(); - tasks = _tasks.ToArray(); + tasks = [.. _tasks]; } try diff --git a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs index 6ebfbc1b253d4..d8fb050b92a0e 100644 --- a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs @@ -68,7 +68,10 @@ public void TestCreateTextUsesByteOrderMarkIfPresent() TestCreateTextInferredEncoding( textFactoryService, - Encoding.UTF8.GetPreamble().Concat(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetBytes("Test")).ToArray(), + [ + .. Encoding.UTF8.GetPreamble(), + .. new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetBytes("Test"), + ], defaultEncoding: Encoding.GetEncoding(1254), expectedEncoding: Encoding.UTF8); } diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index 5685f62e13433..0a13a760a93b9 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -162,7 +162,7 @@ internal void InitializeWorkspace(EditorTestWorkspace workspace) protected static void VerifyNavigateToResultItems( List expecteditems, IEnumerable items) { - expecteditems = expecteditems.OrderBy(i => i.Name).ToList(); + expecteditems = [.. expecteditems.OrderBy(i => i.Name)]; items = items.OrderBy(i => i.Name).ToList(); Assert.Equal(expecteditems.Count(), items.Count()); diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs index 27a7d762f5b1c..1c629b457c386 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs @@ -382,9 +382,9 @@ public void BadPdb_ForwardChain() { switch (token) { - case methodToken1: return new MethodDebugInfoBytes.Builder().AddForward(methodToken2).Build().Bytes.ToArray(); - case methodToken2: return new MethodDebugInfoBytes.Builder().AddForward(methodToken3).Build().Bytes.ToArray(); - case methodToken3: return new MethodDebugInfoBytes.Builder([new[] { importString }]).Build().Bytes.ToArray(); + case methodToken1: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken2).Build().Bytes]; + case methodToken2: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken3).Build().Bytes]; + case methodToken3: return [.. new MethodDebugInfoBytes.Builder([new[] { importString }]).Build().Bytes]; default: throw null; } }); @@ -421,7 +421,7 @@ public void BadPdb_Cycle() { switch (token) { - case methodToken1: return new MethodDebugInfoBytes.Builder().AddForward(methodToken1).Build().Bytes.ToArray(); + case methodToken1: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken1).Build().Bytes]; default: throw null; } }); diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs index 89e03e291c67e..b87194ad1065d 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs @@ -91,7 +91,7 @@ public void Free() public T[] ToArray() { - return _items.ToArray(); + return [.. _items]; } public T[] ToArrayAndFree() diff --git a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs index adbf1368e2732..1da9a346b6325 100644 --- a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs +++ b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs @@ -40,7 +40,7 @@ internal void AddModule(Module module) internal Module[] GetModules() { - return _modules.ToArray(); + return [.. _modules]; } void IDisposable.Dispose() diff --git a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs index 6f9f3d7ad366f..04651d7b9dc9a 100644 --- a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs +++ b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs @@ -73,7 +73,7 @@ internal override Request[] GetRequests(Process process) { return new Request[0]; } - return requests.ToArray(); + return [.. requests]; } internal override string GetRequestModuleName(Request request) diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs b/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs index c46088fa8a4c1..4f43d28015f4f 100644 --- a/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs +++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs @@ -16,7 +16,7 @@ internal static class ReflectionUtilities { internal static Assembly Load(ImmutableArray assembly) { - return Assembly.Load(assembly.ToArray()); + return Assembly.Load([.. assembly]); } internal static object Instantiate(this Type type, params object[] args) diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs index e6f199978569a..60ebe77ff745b 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationName/DeclarationNameCompletionProvider.cs @@ -30,7 +30,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal partial class DeclarationNameCompletionProvider([ImportMany] IEnumerable> recommenders) : LSPCompletionProvider { - private ImmutableArray> Recommenders { get; } = ExtensionOrderer.Order(recommenders).ToImmutableArray(); + private ImmutableArray> Recommenders { get; } = [.. ExtensionOrderer.Order(recommenders)]; internal override string Language => LanguageNames.CSharp; diff --git a/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs b/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs index edfc5c4e75f64..ea9130d85ec3a 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/CSharpConvertLinqQueryToForEachProvider.cs @@ -827,7 +827,7 @@ private StatementSyntax[] GenerateStatements( // The stack was processed in the reverse order, but the extra statements should be provided in the direct order. statements.Reverse(); statements.Add(statement.WithAdditionalAnnotations(Simplifier.Annotation)); - return statements.ToArray(); + return [.. statements]; } private bool TryProcessQueryBody(QueryBodySyntax queryBody, QueryExpressionProcessingInfo queryExpressionProcessingInfo) diff --git a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs index ea8ae8a8da756..54be9ac17f079 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs @@ -124,8 +124,8 @@ void Convert(ExpressionSyntax replacingExpression, SyntaxNode nodeToRemoveIfFoll // Output: // return queryGenerated.ToList(); or return queryGenerated.Count(); replacingExpression = returnStatement.Expression; - leadingTrivia = GetTriviaFromNode(nodeToRemoveIfFollowedByReturn) - .Concat(SyntaxNodeOrTokenExtensions.GetTrivia(replacingExpression)).ToArray(); + leadingTrivia = [.. GetTriviaFromNode(nodeToRemoveIfFollowedByReturn) +, .. SyntaxNodeOrTokenExtensions.GetTrivia(replacingExpression)]; editor.RemoveNode(nodeToRemoveIfFollowedByReturn); } else diff --git a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs index 6cedb5812a4f7..617ca5843708c 100644 --- a/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ConvertPrimaryToRegularConstructor/ConvertPrimaryToRegularConstructorCodeRefactoringProvider.cs @@ -240,7 +240,7 @@ ImmutableDictionary CreateSynthesizedFields() } } - return result.ToImmutableHashSet(); + return [.. result]; } void RemovePrimaryConstructorParameterList() diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index b2c1e3974176b..11ec3287f89a2 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -927,7 +927,7 @@ private static bool AreSimilarActiveStatements(CommonForEachStatementSyntax oldN RoslynDebug.Assert(oldTokens != null); RoslynDebug.Assert(newTokens != null); - return DeclareSameIdentifiers(oldTokens.ToArray(), newTokens.ToArray()); + return DeclareSameIdentifiers([.. oldTokens], [.. newTokens]); } protected override bool AreEquivalentImpl(SyntaxToken oldToken, SyntaxToken newToken) diff --git a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs index 06b21c73514f7..24f428b4d8983 100644 --- a/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs +++ b/src/Features/CSharp/Portable/GenerateMember/GenerateParameterizedMember/CSharpGenerateParameterizedMemberService.cs @@ -59,7 +59,7 @@ protected override ImmutableArray GetCapturedTypeParameter type.GetReferencedTypeParameters(result); } - return result.ToImmutableArray(); + return [.. result]; } protected override ImmutableArray GenerateTypeParameters(CancellationToken cancellationToken) diff --git a/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs b/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs index 2c06e936fc06a..3e439cc5f6b4e 100644 --- a/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs +++ b/src/Features/CSharp/Portable/QuickInfo/CSharpDiagnosticAnalyzerQuickInfoProvider.cs @@ -156,15 +156,15 @@ private static QuickInfoItem CreateQuickInfo(TextSpan location, DiagnosticDescri var idTag = !string.IsNullOrWhiteSpace(descriptor.HelpLinkUri) ? new TaggedText(TextTags.Text, descriptor.Id, TaggedTextStyle.None, descriptor.HelpLinkUri, descriptor.HelpLinkUri) : new TaggedText(TextTags.Text, descriptor.Id); - return QuickInfoItem.Create(location, sections: new[] - { - QuickInfoSection.Create(QuickInfoSectionKinds.Description, new[] - { + return QuickInfoItem.Create(location, sections: + [ + QuickInfoSection.Create(QuickInfoSectionKinds.Description, + [ idTag, new TaggedText(TextTags.Punctuation, ":"), new TaggedText(TextTags.Space, " "), new TaggedText(TextTags.Text, description) - }.ToImmutableArray()) - }.ToImmutableArray(), relatedSpans: relatedSpans.ToImmutableArray()); + ]) + ], relatedSpans: [.. relatedSpans]); } } diff --git a/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs b/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index 7d9e4f7c66ec5..9e42352af2e27 100644 --- a/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs +++ b/src/Features/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs @@ -27,7 +27,7 @@ internal static void VerifyLineEdits( VerifyLineEdits( editScript, - new[] { new SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, lineEdits.ToImmutableArray()) }, + new[] { new SequencePointUpdates(editScript.Match.OldRoot.SyntaxTree.FilePath, [.. lineEdits]) }, semanticEdits, diagnostics, capabilities); diff --git a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 1eac532fdd3bb..7967d1f73cb8a 100644 --- a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -3679,7 +3679,7 @@ public void Record_Property_Delete_ReplacingCustomWithSynthesized_WithBodyAndMet expectedEdits.Add(SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); edits.VerifySemantics( - expectedEdits.ToArray(), + [.. expectedEdits], capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); } diff --git a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs index 99094f7dcdd97..0a80ac10649b0 100644 --- a/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs +++ b/src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs @@ -192,7 +192,7 @@ await FindResultsInAllSymbolsInStartingProjectAsync( } } - return allReferences.ToImmutableArray(); + return [.. allReferences]; } private static async Task FindResultsInAllSymbolsInStartingProjectAsync( diff --git a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs index 57508f95172f4..0225e66444854 100644 --- a/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs +++ b/src/Features/Core/Portable/AddImport/SymbolReferenceFinder.cs @@ -138,12 +138,11 @@ private async Task> DoAsync(SearchScope searchSc private ImmutableArray DeDupeAndSortReferences(ImmutableArray allReferences) { - return allReferences + return [.. allReferences .Distinct() .Where(NotNull) .Where(NotGlobalNamespace) - .OrderBy((r1, r2) => r1.CompareTo(_document, r2)) - .ToImmutableArray(); + .OrderBy((r1, r2) => r1.CompareTo(_document, r2))]; } private static void CalculateContext( diff --git a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs index 2dfef354517ad..3c1d7a5391de9 100644 --- a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs +++ b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs @@ -392,10 +392,10 @@ internal static ImmutableArray GetCodeStyleOptionsForDiagnostic(Diagno { if (IDEDiagnosticIdToOptionMappingHelper.TryGetMappedOptions(diagnostic.Id, project.Language, out var options)) { - return (from option in options + return [.. (from option in options where option.DefaultValue is ICodeStyleOption orderby option.Definition.ConfigName - select option).ToImmutableArray(); + select option)]; } return []; diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs index 9bb5419aa19c0..403b33e0bad50 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs @@ -110,7 +110,7 @@ internal abstract class AbstractSuppressionBatchFixAllProvider : FixAllProvider await Task.WhenAll(tasks).ConfigureAwait(false); } - return fixesBag.ToImmutableArray(); + return [.. fixesBag]; } private async Task AddDocumentFixesAsync( diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs index c748295d4eabb..e0c7a073532e0 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.PragmaHelpers.cs @@ -164,11 +164,11 @@ internal static SyntaxToken GetNewEndTokenWithAddedPragma( var isEOF = fixer.IsEndOfFileToken(endToken); if (isEOF) { - trivia = endToken.LeadingTrivia.ToImmutableArray(); + trivia = [.. endToken.LeadingTrivia]; } else { - trivia = endToken.TrailingTrivia.ToImmutableArray(); + trivia = [.. endToken.TrailingTrivia]; } var index = GetPositionForPragmaInsertion(trivia, currentDiagnosticSpan, fixer, isStartToken: false, triviaAtIndex: out var insertBeforeTrivia); diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs index c03dc33e08ad9..41b29ab486f0a 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionCodeFixProvider.RemoveSuppressionCodeAction.BatchFixer.cs @@ -153,7 +153,7 @@ public override async Task TryGetMergedFixAsync( } return await base.TryGetMergedFixAsync( - newBatchOfFixes.ToImmutableArray(), fixAllState, progressTracker, cancellationToken).ConfigureAwait(false); + [.. newBatchOfFixes], fixAllState, progressTracker, cancellationToken).ConfigureAwait(false); } private static async Task> GetAttributeNodesToFixAsync(ImmutableArray attributeRemoveFixes, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs b/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs index 076e4ddaabbdb..5a0a535ac915c 100644 --- a/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs +++ b/src/Features/Core/Portable/CodeLens/CodeLensFindReferenceProgress.cs @@ -48,7 +48,7 @@ internal sealed class CodeLensFindReferencesProgress( public int ReferencesCount => _locations.Count; - public ImmutableArray Locations => _locations.ToImmutableArray(); + public ImmutableArray Locations => [.. _locations]; public void OnStarted() { diff --git a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs index 77cffe24feeb2..b8daab42b776b 100644 --- a/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/CodeRefactoringService.cs @@ -212,7 +212,7 @@ private class ProjectCodeRefactoringProvider : AbstractProjectExtensionProvider { protected override ImmutableArray GetLanguages(ExportCodeRefactoringProviderAttribute exportAttribute) - => exportAttribute.Languages.ToImmutableArray(); + => [.. exportAttribute.Languages]; protected override bool TryGetExtensionsFromReference(AnalyzerReference reference, out ImmutableArray extensions) { diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index 6a945ab96565f..8845c42bdabdb 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -259,7 +259,7 @@ await ChangeNamespaceInSingleDocumentAsync(solutionAfterNamespaceChange, documen solutionAfterImportsRemoved = await RemoveUnnecessaryImportsAsync( solutionAfterImportsRemoved, - referenceDocuments.ToImmutableArray(), + [.. referenceDocuments], [declaredNamespace, targetNamespace], fallbackOptions, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs index 9f9afa686ebeb..5a7dbf9fb95b9 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractSyncNamespaceCodeRefactoringProvider.State.cs @@ -158,7 +158,7 @@ private static bool IsDocumentPathRootedInProjectFolder(Document document) if (projectRoot is null) return false; - var folderPath = Path.Combine(document.Folders.ToArray()); + var folderPath = Path.Combine([.. document.Folders]); var logicalDirectoryPath = PathUtilities.CombineAbsoluteAndRelativePaths(projectRoot, folderPath); if (logicalDirectoryPath is null) return false; diff --git a/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs b/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs index 9115fa8976a73..b05169033da52 100644 --- a/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs +++ b/src/Features/Core/Portable/Completion/CharacterSetModificationRule.cs @@ -43,5 +43,5 @@ public static CharacterSetModificationRule Create(CharacterSetModificationKind k /// One or more characters. These are typically punctuation characters. /// public static CharacterSetModificationRule Create(CharacterSetModificationKind kind, params char[] characters) - => new(kind, characters.ToImmutableArray()); + => new(kind, [.. characters]); } diff --git a/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs b/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs index 9c707d7f8cb70..5095803afa12f 100644 --- a/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs +++ b/src/Features/Core/Portable/Completion/CompletionService_GetCompletions.cs @@ -137,13 +137,13 @@ ImmutableArray GetTriggeredProviders( var triggeredProviders = providers.Where(p => p.ShouldTriggerCompletion(document.Project.Services, text, caretPosition, trigger, options, passThroughOptions)).ToImmutableArrayOrEmpty(); Debug.Assert(ValidatePossibleTriggerCharacterSet(trigger.Kind, triggeredProviders, document, text, caretPosition, options)); - return triggeredProviders.IsEmpty ? providers.ToImmutableArray() : triggeredProviders; + return triggeredProviders.IsEmpty ? [.. providers] : triggeredProviders; } return []; default: - return providers.ToImmutableArray(); + return [.. providers]; } } diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs index f3a276a0b9718..8417361ead2dd 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractSymbolCompletionProvider.cs @@ -277,7 +277,7 @@ private async Task> GetItemsAsync( var totalProjects = contextAndSymbolLists.Select(t => t.documentId.ProjectId).ToList(); return CreateItems( - completionContext, symbolToContextMap.Keys.ToImmutableArray(), symbol => symbolToContextMap[symbol], missingSymbolsMap, totalProjects); + completionContext, [.. symbolToContextMap.Keys], symbol => symbolToContextMap[symbol], missingSymbolsMap, totalProjects); } protected virtual bool IsExclusive() diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs index 28b81bce83aa4..1231d66758f4b 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.SymbolComputer.cs @@ -482,7 +482,7 @@ private static ImmutableArray GetReceiverTypeNames(ITypeSymbol receiverT { using var _ = PooledHashSet.GetInstance(out var allTypeNamesBuilder); AddNamesForTypeWorker(receiverTypeSymbol, allTypeNamesBuilder); - return allTypeNamesBuilder.ToImmutableArray(); + return [.. allTypeNamesBuilder]; static void AddNamesForTypeWorker(ITypeSymbol receiverTypeSymbol, PooledHashSet builder) { diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs index 58af5842ea690..1e7f0aed56232 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs @@ -72,7 +72,7 @@ public static void WarmUpCacheInCurrentProcess(Project project) var remoteResult = await client.TryInvokeAsync( project, (service, solutionInfo, cancellationToken) => service.GetUnimportedExtensionMethodsAsync( - solutionInfo, document.Id, position, receiverTypeSymbolKeyData, namespaceInScope.ToImmutableArray(), + solutionInfo, document.Id, position, receiverTypeSymbolKeyData, [.. namespaceInScope], targetTypesSymbolKeyData, forceCacheCreation, hideAdvancedMembers, cancellationToken), cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs b/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs index 869ffdd646947..a4739b0a46790 100644 --- a/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs +++ b/src/Features/Core/Portable/Completion/Providers/RecommendedKeyword.cs @@ -40,6 +40,6 @@ internal static ImmutableArray CreateDisplayParts(string keyw textContentBuilder.AddText(toolTip); } - return textContentBuilder.ToImmutableArray(); + return [.. textContentBuilder]; } } diff --git a/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs b/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs index 1bdb4ab26c64d..7e41ac5d80b2f 100644 --- a/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs +++ b/src/Features/Core/Portable/Debugging/AbstractBreakpointResolver.cs @@ -147,7 +147,7 @@ private async Task> FindMembersAsync( default: // They have a namespace or nested type qualified name. Walk up to the root namespace trying to match. var containers = await _solution.GetGlobalNamespacesAsync(cancellationToken).ConfigureAwait(false); - return FindMembers(containers, nameParts.ToArray()); + return FindMembers(containers, [.. nameParts]); } } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs index 071b40d5e7440..ced9d33433ef3 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs @@ -435,7 +435,7 @@ private static ImmutableDictionary> GroupToImmutableDiction foreach (var item in items) { - builder.Add(item.Key, item.ToImmutableArray()); + builder.Add(item.Key, [.. item]); } return builder.ToImmutable(); @@ -841,7 +841,7 @@ public ImmutableHashSet GetModulesPreparedForUpdate() { lock (_instance._modulesPreparedForUpdateGuard) { - return _instance._modulesPreparedForUpdate.ToImmutableHashSet(); + return [.. _instance._modulesPreparedForUpdate]; } } diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs index 2b3d5186583d6..2a9b478b6a437 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs @@ -15,7 +15,7 @@ internal sealed class DebuggingSessionTelemetry(Guid solutionSessionId) internal readonly struct Data(DebuggingSessionTelemetry telemetry) { public readonly Guid SolutionSessionId = telemetry._solutionSessionId; - public readonly ImmutableArray EditSessionData = telemetry._editSessionData.ToImmutableArray(); + public readonly ImmutableArray EditSessionData = [.. telemetry._editSessionData]; public readonly int EmptyEditSessionCount = telemetry._emptyEditSessionCount; public readonly int EmptyHotReloadEditSessionCount = telemetry._emptyHotReloadEditSessionCount; } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs index 90c463d540cf9..9167133f77cbf 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs @@ -45,7 +45,7 @@ public async ValueTask> GetDocumentAnaly var tasks = documents.Select(document => Task.Run(() => GetDocumentAnalysisAsync(oldSolution, document.oldDocument, document.newDocument, activeStatementSpanProvider, cancellationToken).AsTask(), cancellationToken)); var allResults = await Task.WhenAll(tasks).ConfigureAwait(false); - return allResults.ToImmutableArray(); + return [.. allResults]; } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs index 1d69bf53c5dd6..c62f2c83f7ef4 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs @@ -236,7 +236,7 @@ internal static bool TryGetDocumentChecksum(ISymUnmanagedReader5 symReader, stri } algorithmId = symDocument.GetHashAlgorithm(); - checksum = symDocument.GetChecksum().ToImmutableArray(); + checksum = [.. symDocument.GetChecksum()]; return true; } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs index ee551a3796cd7..c1ceea78751b5 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs @@ -109,7 +109,7 @@ private ImmutableArray GetActiveDebuggingSessions() { lock (_debuggingSessions) { - return _debuggingSessions.ToImmutableArray(); + return [.. _debuggingSessions]; } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index fc558099aebaf..19f640b73f83a 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -750,7 +750,7 @@ internal static void MergePartialEdits( if (edits.Count == mergedEditsBuilder.Count) { mergedEdits = mergedEditsBuilder.ToImmutable(); - addedSymbols = addedSymbolsBuilder.ToImmutableHashSet(); + addedSymbols = [.. addedSymbolsBuilder]; return; } @@ -804,7 +804,7 @@ internal static void MergePartialEdits( } mergedEdits = mergedEditsBuilder.ToImmutable(); - addedSymbols = addedSymbolsBuilder.ToImmutableHashSet(); + addedSymbols = [.. addedSymbolsBuilder]; } public async ValueTask EmitSolutionUpdateAsync(Solution solution, ActiveStatementSpanProvider solutionActiveStatementSpanProvider, UpdateId updateId, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/EditAndContinue/TraceLog.cs b/src/Features/Core/Portable/EditAndContinue/TraceLog.cs index 8c0afaf35a6fd..a96471c54ad53 100644 --- a/src/Features/Core/Portable/EditAndContinue/TraceLog.cs +++ b/src/Features/Core/Portable/EditAndContinue/TraceLog.cs @@ -159,7 +159,7 @@ public void Write(DebuggingSessionId sessionId, ImmutableArray bytes, stri try { path = Path.Combine(CreateSessionDirectory(sessionId, directory), fileName); - File.WriteAllBytes(path, bytes.ToArray()); + File.WriteAllBytes(path, [.. bytes]); } catch (Exception e) { diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs index af01a2f0785ee..349b799dcf13d 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.cs @@ -214,7 +214,7 @@ private static async Task ExpandAsync(TSelectionResult selecti } private ImmutableArray GetFormattingRules(Document document) - => ImmutableArray.Create(GetCustomFormattingRule(document)).AddRange(Formatter.GetDefaultFormattingRules(document)); + => [GetCustomFormattingRule(document), .. Formatter.GetDefaultFormattingRules(document)]; private OperationStatus CheckVariableTypes( OperationStatus status, diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs index b5c8b72f60d31..30c8390088926 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService.DefinitionTrackingContext.cs @@ -59,7 +59,7 @@ public ImmutableArray GetDefinitions() { lock (_gate) { - return _definitions.ToImmutableArray(); + return [.. _definitions]; } } } diff --git a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs index 18ef1f1dd5451..9e2ff048580f5 100644 --- a/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs +++ b/src/Features/Core/Portable/FindUsages/AbstractFindUsagesService_FindImplementations.cs @@ -120,7 +120,7 @@ private static async Task> FindSourceImplementationsAsyn } } - return builder.ToImmutableArray(); + return [.. builder]; static bool AddedAllLocations(ISymbol implementation, HashSet<(string filePath, TextSpan span)> seenLocations) { @@ -153,7 +153,7 @@ private static async Task> FindImplementationsWorkerAsyn } } - return result.ToImmutableArray(); + return [.. result]; } private static async Task> FindSourceAndMetadataImplementationsAsync( @@ -189,7 +189,7 @@ private static async Task> FindSourceAndMetadataImplemen implementationsAndOverrides.Add(symbol); } - return implementationsAndOverrides.ToImmutableArray(); + return [.. implementationsAndOverrides]; } else if (symbol is INamedTypeSymbol { TypeKind: TypeKind.Class } namedType) { diff --git a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs index 5ed4e014de204..590f45b35393c 100644 --- a/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/GenerateEqualsAndGetHashCodeFromMembers/GenerateEqualsAndGetHashCodeFromMembersCodeRefactoringProvider.cs @@ -236,7 +236,7 @@ private async Task> CreateActionsAsync( } var codeActions = await Task.WhenAll(tasks).ConfigureAwait(false); - return codeActions.ToImmutableArray(); + return [.. codeActions]; } diff --git a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs index 8ffdab80fa802..7cd296978ce7b 100644 --- a/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs +++ b/src/Features/Core/Portable/GenerateMember/GenerateParameterizedMember/AbstractGenerateParameterizedMemberService.AbstractInvocationInfo.cs @@ -62,7 +62,7 @@ private ITypeParameterSymbol MassageTypeParameter( var nonClassTypes = constraints.Where(ts => ts.TypeKind != TypeKind.Class).ToList(); classTypes = MergeClassTypes(classTypes); - constraints = classTypes.Concat(nonClassTypes).ToList(); + constraints = [.. classTypes, .. nonClassTypes]; if (constraints.SequenceEqual(typeParameter.ConstraintTypes)) { return typeParameter; diff --git a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs index 48d4dfc643a5f..8adaaeda55199 100644 --- a/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs +++ b/src/Features/Core/Portable/GenerateType/AbstractGenerateTypeService.Editor.cs @@ -313,7 +313,7 @@ private async Task> GetGenerateInNewFileOper ? folders : _state.SimpleName != _state.NameOrMemberAccessExpression ? containers.ToList() - : _semanticDocument.Document.Folders.ToList(); + : [.. _semanticDocument.Document.Folders]; if (newDocument.Project.Language == _semanticDocument.Document.Project.Language) { @@ -524,8 +524,8 @@ private async Task> GetGenerateIntoExistingD // Populate the ContainerList AddFoldersToNamespaceContainers(containerList, folders); - containers = containerList.ToArray(); - includeUsingsOrImports = string.Join(".", containerList.ToArray()); + containers = [.. containerList]; + includeUsingsOrImports = string.Join(".", [.. containerList]); } // Case 4 : If the type is generated into the same VB project or @@ -538,8 +538,8 @@ private async Task> GetGenerateIntoExistingD { // Populate the ContainerList AddFoldersToNamespaceContainers(containerList, folders); - containers = containerList.ToArray(); - includeUsingsOrImports = string.Join(".", containerList.ToArray()); + containers = [.. containerList]; + includeUsingsOrImports = string.Join(".", [.. containerList]); if (!string.IsNullOrWhiteSpace(rootNamespaceOfTheProjectGeneratedInto)) { includeUsingsOrImports = string.IsNullOrEmpty(includeUsingsOrImports) diff --git a/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs b/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs index e5fe8ea9ea5f1..4e17a45b3c04e 100644 --- a/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs +++ b/src/Features/Core/Portable/InheritanceMargin/InheritanceMarginItem.cs @@ -71,5 +71,5 @@ public bool Equals(InheritanceMarginItem other) => targetItems.IsEmpty ? null : new(lineNumber, topLevelDisplayText, displayTexts, glyph, Order(targetItems)); public static ImmutableArray Order(ImmutableArray targetItems) - => targetItems.OrderBy(t => t.DisplayName).ThenByDescending(t => t.LanguageGlyph).ThenBy(t => t.ProjectName ?? "").ToImmutableArray(); + => [.. targetItems.OrderBy(t => t.DisplayName).ThenByDescending(t => t.LanguageGlyph).ThenBy(t => t.ProjectName ?? "")]; } diff --git a/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs b/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs index 1848fe2b0847d..3f7c1c32c7e43 100644 --- a/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs +++ b/src/Features/Core/Portable/InlineHints/InlineHintHelpers.cs @@ -54,7 +54,7 @@ private static async Task> GetDescriptionAsync(Docume } } - return parts.ToImmutableArray(); + return [.. parts]; } return default; diff --git a/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs b/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs index efadd9d050f2d..0d890aadf1f0b 100644 --- a/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs +++ b/src/Features/Core/Portable/InlineMethod/AbstractInlineMethodRefactoringProvider.InlineContext.cs @@ -395,7 +395,7 @@ public static ImmutableHashSet GetAllSymbols( var operation = semanticModel.GetOperation(methodDeclarationSyntax, cancellationToken); visitor.Visit(operation); - return visitor._allSymbols.ToImmutableHashSet(); + return [.. visitor._allSymbols]; } public override void Visit(IOperation? operation) diff --git a/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs b/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs index a6fe4acda7d4e..23e2bab3d707f 100644 --- a/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs +++ b/src/Features/Core/Portable/LanguageServices/AnonymousTypeDisplayService/AbstractStructuralTypeDisplayService.cs @@ -148,7 +148,7 @@ private static ImmutableArray OrderStructuralTypes( { if (symbol is IMethodSymbol method) { - return structuralTypes.OrderBy( + return [.. structuralTypes.OrderBy( (n1, n2) => { var index1 = method.TypeArguments.IndexOf(n1); @@ -157,11 +157,11 @@ private static ImmutableArray OrderStructuralTypes( index2 = index2 < 0 ? int.MaxValue : index2; return index1 - index2; - }).ToImmutableArray(); + })]; } else if (symbol is IPropertySymbol property) { - return structuralTypes.OrderBy( + return [.. structuralTypes.OrderBy( (n1, n2) => { if (n1.Equals(property.ContainingType) && !n2.Equals(property.ContainingType)) @@ -176,7 +176,7 @@ private static ImmutableArray OrderStructuralTypes( { return 0; } - }).ToImmutableArray(); + })]; } return structuralTypes; diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs index fe3febe0beac0..d0abbcdeb54b1 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs @@ -30,7 +30,7 @@ internal class MetadataAsSourceFileService([ImportMany] IEnumerable - private readonly ImmutableArray> _providers = ExtensionOrderer.Order(providers).ToImmutableArray(); + private readonly ImmutableArray> _providers = [.. ExtensionOrderer.Order(providers)]; /// /// Workspace created the first time we generate any metadata for any symbol. diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs index bd27fae1aa8bf..3101d90c3049b 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs @@ -37,7 +37,7 @@ public MetadataAsSourceGeneratedFileInfo(string rootPath, Workspace sourceWorksp ? sourceProject.ParseOptions : sourceProject.Solution.Services.GetLanguageServices(LanguageName).GetRequiredService().GetDefaultParseOptionsWithLatestLanguageVersion(); - this.References = sourceProject.MetadataReferences.ToImmutableArray(); + this.References = [.. sourceProject.MetadataReferences]; this.AssemblyIdentity = topLevelNamedType.ContainingAssembly.Identity; var extension = LanguageName == LanguageNames.CSharp ? ".cs" : ".vb"; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 3a0fd67c501d9..41ba1ca6bbe81 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -84,7 +84,7 @@ public async Task SearchCachedDocumentsAsync( var callback = new NavigateToSearchServiceCallback(onItemFound, onProjectCompleted); await client.TryInvokeAsync( (service, callbackId, cancellationToken) => - service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs index 5037c87b9bbc0..ee18144bee5f8 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.GeneratedDocumentSearch.cs @@ -43,7 +43,7 @@ await client.TryInvokeAsync( // compilations would not be shared and we'd have to rebuild them. solution, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchGeneratedDocumentsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchGeneratedDocumentsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs index cfc2f549f72ba..d434504af767c 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.NormalSearch.cs @@ -35,7 +35,7 @@ public async Task SearchDocumentAsync( await client.TryInvokeAsync( document.Project, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchDocumentAsync(solutionInfo, document.Id, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchDocumentAsync(solutionInfo, document.Id, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; @@ -81,7 +81,7 @@ await client.TryInvokeAsync( // on the oop side. solution, (service, solutionInfo, callbackId, cancellationToken) => - service.SearchProjectsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), priorityDocumentIds, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken), + service.SearchProjectsAsync(solutionInfo, projects.SelectAsArray(p => p.Id), priorityDocumentIds, searchPattern, [.. kinds], callbackId, cancellationToken), callback, cancellationToken).ConfigureAwait(false); return; diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs index fc90beeb18284..af9ee04c459d0 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs @@ -381,7 +381,7 @@ async Task SearchCoreAsync(IGrouping grouping var searchService = grouping.Key; await processProjectAsync( searchService, - grouping.ToImmutableArray(), + [.. grouping], (project, result) => { // If we're seeing a dupe in another project, then filter it out here. The results from diff --git a/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs b/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs index 193ffd4030719..b036ced7dcf76 100644 --- a/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs +++ b/src/Features/Core/Portable/PullMemberUp/AbstractPullMemberUpRefactoringProvider.cs @@ -104,7 +104,7 @@ private static ImmutableArray FindAllValidDestinations( { var allDestinations = selectedMembers.All(m => m.IsKind(SymbolKind.Field)) ? containingType.GetBaseTypes().ToImmutableArray() - : containingType.AllInterfaces.Concat(containingType.GetBaseTypes()).ToImmutableArray(); + : [.. containingType.AllInterfaces, .. containingType.GetBaseTypes()]; return allDestinations.WhereAsArray(destination => MemberAndDestinationValidator.IsDestinationValid(solution, destination, cancellationToken)); } diff --git a/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs b/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs index 0ab3b58f2910a..7091430a3c1b0 100644 --- a/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs +++ b/src/Features/Core/Portable/PullMemberUp/MembersPuller.cs @@ -489,7 +489,7 @@ private static async Task @ref.GetSyntaxAsync(cancellationToken)); var allSyntaxes = await Task.WhenAll(tasks).ConfigureAwait(false); - symbolToDeclarationsBuilder.Add(memberAnalysisResult.Member, allSyntaxes.ToImmutableArray()); + symbolToDeclarationsBuilder.Add(memberAnalysisResult.Member, [.. allSyntaxes]); } return symbolToDeclarationsBuilder.ToImmutableDictionary(); diff --git a/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs b/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs index d8e20f3fe9c71..73580d5c070a1 100644 --- a/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs +++ b/src/Features/Core/Portable/QuickInfo/CommonSemanticQuickInfoProvider.cs @@ -164,10 +164,10 @@ protected static Task CreateContentAsync( // if generating quick info for an attribute, prefer bind to the class instead of the constructor if (syntaxFactsService.IsAttributeName(token.Parent!)) { - symbols = symbols.OrderBy((s1, s2) => + symbols = [.. symbols.OrderBy((s1, s2) => s1.Kind == s2.Kind ? 0 : s1.Kind == SymbolKind.NamedType ? -1 : - s2.Kind == SymbolKind.NamedType ? 1 : 0).ToImmutableArray(); + s2.Kind == SymbolKind.NamedType ? 1 : 0)]; } return QuickInfoUtilities.CreateQuickInfoItemAsync( diff --git a/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs b/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs index 09fbb8c8c6fbe..9bbcd5aac43f3 100644 --- a/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs +++ b/src/Features/Core/Portable/QuickInfo/IndentationHelper.cs @@ -73,7 +73,7 @@ public static ImmutableArray GetSpansWithAlignedIndentation( } } - return adjustedClassifiedSpans.ToImmutableArray(); + return [.. adjustedClassifiedSpans]; } else { diff --git a/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs b/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs index 4e1f9c472db23..4326eef9430da 100644 --- a/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs +++ b/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs @@ -68,7 +68,7 @@ public static async Task> GetUnionItemsFromDocumentAndLinkedDo totalItems.AddRange(values.NullToEmpty()); } - return totalItems.ToImmutableArray(); + return [.. totalItems]; } public static async Task IsValidContextForDocumentOrLinkedDocumentsAsync( diff --git a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs index 8043821cf95c2..c871967799ac1 100644 --- a/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs +++ b/src/Features/Core/Portable/Shared/Extensions/ISymbolExtensions_Sorting.cs @@ -36,8 +36,7 @@ public static ImmutableArray Sort( var symbolToParameterTypeNames = new ConcurrentDictionary(); string[] getParameterTypeNames(TSymbol s) => GetParameterTypeNames(s, semanticModel, position); - return symbols.OrderBy((s1, s2) => Compare(s1, s2, symbolToParameterTypeNames, getParameterTypeNames)) - .ToImmutableArray(); + return [.. symbols.OrderBy((s1, s2) => Compare(s1, s2, symbolToParameterTypeNames, getParameterTypeNames))]; } private static INamedTypeSymbol GetNamedType(ITypeSymbol type) diff --git a/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs b/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs index 9016af082906d..d77c21b9257f6 100644 --- a/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs +++ b/src/Features/Core/Portable/SignatureHelp/AbstractSignatureHelpProvider.cs @@ -101,7 +101,7 @@ private static (IList items, int? selectedItem) Filter(IList< var filteredList = items.Where(i => Include(i, parameterNames)).ToList(); var isEmpty = filteredList.Count == 0; if (!selectedItem.HasValue || isEmpty) - return (isEmpty ? items.ToList() : filteredList, selectedItem); + return (isEmpty ? [.. items] : filteredList, selectedItem); // adjust the selected item var selection = items[selectedItem.Value]; diff --git a/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs b/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs index 0591664252acf..523087d5364de 100644 --- a/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs +++ b/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs @@ -106,6 +106,6 @@ private static ImmutableArray GetFileMatches(Solution solution, StackF } } - return potentialMatches.ToImmutableArray(); + return [.. potentialMatches]; } } diff --git a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs index 6b52fabbaec56..f70b8941c613c 100644 --- a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs +++ b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs @@ -256,9 +256,8 @@ internal static ImmutableArray GetAllCompilationAssemblies(ReferenceInfo { var transitiveCompilationAssemblies = reference.Dependencies .SelectMany(dependency => GetAllCompilationAssemblies(dependency)); - return reference.CompilationAssemblies - .Concat(transitiveCompilationAssemblies) - .ToImmutableArray(); + return [.. reference.CompilationAssemblies +, .. transitiveCompilationAssemblies]; } public static async Task UpdateReferencesAsync( diff --git a/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs b/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs index 0720c59837c96..36cc24bc78513 100644 --- a/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs +++ b/src/Features/Core/Portable/ValueTracking/ValueTrackingProgressCollector.cs @@ -33,7 +33,7 @@ public ImmutableArray GetItems() { lock (_lock) { - return _items.ToImmutableArray(); + return [.. _items]; } } diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs index 404e302782684..b2da22f5e8157 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/LspFileChangeWatcher.cs @@ -42,7 +42,7 @@ public static bool SupportsLanguageServerHost(LanguageServerHost languageServerH public IFileChangeContext CreateContext(params WatchedDirectory[] watchedDirectories) { - return new FileChangeContext(watchedDirectories.ToImmutableArray(), this); + return new FileChangeContext([.. watchedDirectories], this); } private class FileChangeContext : IFileChangeContext diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs index 5b21bd69e817a..3f901a8be52b8 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/FileWatching/SimpleFileChangeWatcher.cs @@ -19,7 +19,7 @@ internal sealed class SimpleFileChangeWatcher : IFileChangeWatcher { public IFileChangeContext CreateContext(params WatchedDirectory[] watchedDirectories) { - return new FileChangeContext(watchedDirectories.ToImmutableArray()); + return new FileChangeContext([.. watchedDirectories]); } private class FileChangeContext : IFileChangeContext diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs index b178281cb3bed..f68f4a7e7d536 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectSystem.cs @@ -185,7 +185,7 @@ private async ValueTask LoadOrReloadProjectsAsync(ImmutableSegmentedList project Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through."); var languageServerManager = LanguageServerHost.Instance.GetRequiredLspService(); - var unresolvedParams = new UnresolvedDependenciesParams(projectPaths.ToArray()); + var unresolvedParams = new UnresolvedDependenciesParams([.. projectPaths]); await languageServerManager.SendRequestAsync(ProjectNeedsRestoreName, unresolvedParams, cancellationToken); } diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs index 353aeb665de82..b6142d715962b 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs @@ -40,7 +40,7 @@ public async Task AddAdditionalFilesAsync(IReadOnlyList addition await using var _ = disposableBatchScope.ConfigureAwait(false); foreach (var additionalFile in additionalFiles) - _project.AddAdditionalFile(additionalFile.FilePath, folders: additionalFile.FolderNames.ToImmutableArray()); + _project.AddAdditionalFile(additionalFile.FilePath, folders: [.. additionalFile.FolderNames]); } public async Task AddAnalyzerConfigFilesAsync(IReadOnlyList analyzerConfigPaths, CancellationToken cancellationToken) @@ -85,7 +85,7 @@ public async Task AddSourceFilesAsync(IReadOnlyList sourceFiles, await using var _ = disposableBatchScope.ConfigureAwait(false); foreach (var sourceFile in sourceFiles) - _project.AddSourceFile(sourceFile.FilePath, folders: sourceFile.FolderNames.ToImmutableArray()); + _project.AddSourceFile(sourceFile.FilePath, folders: [.. sourceFile.FolderNames]); } public void Dispose() @@ -190,7 +190,7 @@ public async Task SetCommandLineArgumentsAsync(IReadOnlyList arguments, var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); await using var _ = disposableBatchScope.ConfigureAwait(false); - _optionsProcessor.SetCommandLine(arguments.ToImmutableArray()); + _optionsProcessor.SetCommandLine([.. arguments]); } public async Task SetDisplayNameAsync(string displayName, CancellationToken cancellationToken) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs index 9f91e4a0a2525..7f57a870a5cec 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/Restore/RestoreHandler.cs @@ -87,7 +87,7 @@ private static ImmutableArray GetRestorePaths(RestoreParams request, Sol { if (request.ProjectFilePaths.Any()) { - return request.ProjectFilePaths.ToImmutableArray(); + return [.. request.ProjectFilePaths]; } // No file paths were specified - this means we should restore all projects in the solution. diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs index 69df0964d0b8f..a166543886f52 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Testing/TestDiscoverer.DiscoveryHandler.cs @@ -52,7 +52,7 @@ public void HandleRawMessage(string rawMessage) public ImmutableArray GetTestCases() { Contract.ThrowIfFalse(_isComplete, "Tried to get test cases before discovery completed"); - return _testCases.ToImmutableArray(); + return [.. _testCases]; } public bool IsAborted() diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs index 1f8c71ce2d7ab..c3fb6f1dbc1db 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs @@ -45,6 +45,6 @@ public LanguageServerEndpointAttribute(string method) public LanguageServerEndpointAttribute(string method, string language, params string[] additionalLanguages) { Method = method; - Languages = new[] { language }.Concat(additionalLanguages).ToArray(); + Languages = [language, .. additionalLanguages]; } } diff --git a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs index 2b2b974ac5a7c..8eb7e16616167 100644 --- a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs +++ b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs @@ -98,7 +98,7 @@ public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) Range = true, Legend = new SemanticTokensLegend { - TokenTypes = SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes.ToArray(), + TokenTypes = [.. SemanticTokensSchema.GetSchema(clientCapabilities.HasVisualStudioLspCapability()).AllTokenTypes], TokenModifiers = SemanticTokensSchema.TokenModifiers } }; diff --git a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index c6a06c30f422c..a06cee02e928e 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -390,7 +390,7 @@ public static LSP.Range TextSpanToRange(TextSpan textSpan, SourceText text) else { var newText = await newDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - textChanges = newText.GetTextChanges(oldText).ToImmutableArray(); + textChanges = [.. newText.GetTextChanges(oldText)]; } // Map all the text changes' spans for this document. diff --git a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs index f30b9ac390f0d..fde0bc07d9891 100644 --- a/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs +++ b/src/Features/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs @@ -15,7 +15,7 @@ private class ProjectCodeFixProvider : AbstractProjectExtensionProvider { protected override ImmutableArray GetLanguages(ExportCodeFixProviderAttribute exportAttribute) - => exportAttribute.Languages.ToImmutableArray(); + => [.. exportAttribute.Languages]; protected override bool TryGetExtensionsFromReference(AnalyzerReference reference, out ImmutableArray extensions) { diff --git a/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs b/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs index 00d5807883c73..e68ef976c26f6 100644 --- a/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs +++ b/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs @@ -64,7 +64,7 @@ public PEFile TryResolve(MetadataReference metadataReference, PEStreamOptions st { if (_inMemoryImagesForTesting.TryGetValue(metadataReference, out var pair)) { - return new PEFile(pair.fileName, new MemoryStream(pair.image.ToArray()), streamOptions); + return new PEFile(pair.fileName, new MemoryStream([.. pair.image]), streamOptions); } return null; diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs index 17751bcd9e7d0..f829aa1582150 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs @@ -214,7 +214,7 @@ private static async Task a // order statesets // order will be in this order // BuiltIn Compiler Analyzer (C#/VB) < Regular DiagnosticAnalyzers < Document/ProjectDiagnosticAnalyzers - OrderedStateSets = StateSetMap.Values.OrderBy(PriorityComparison).ToImmutableArray(); + OrderedStateSets = [.. StateSetMap.Values.OrderBy(PriorityComparison)]; } public HostAnalyzerStateSets WithExcludedAnalyzers(ImmutableHashSet excludedAnalyzers) diff --git a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs index 75513230f0080..0a05dd8864c9f 100644 --- a/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs +++ b/src/Features/LanguageServer/Protocol/Features/UnifiedSuggestions/UnifiedSuggestedActionsSource.cs @@ -401,7 +401,7 @@ private static void AddUnifiedSuggestedActionsSet( sets.Add(new UnifiedSuggestedActionSet( originalSolution, category, - group.ToImmutableArray(), + [.. group], title: null, priority, applicableToSpan: groupKey.Item1.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text))); @@ -711,9 +711,7 @@ private static ImmutableArray GetInitiallyOrderedActi private static ImmutableArray OrderActionSets( ImmutableArray actionSets, TextSpan? selectionOpt) { - return actionSets.OrderByDescending(s => s.Priority) - .ThenBy(s => s, new UnifiedSuggestedActionSetComparer(selectionOpt)) - .ToImmutableArray(); + return [.. actionSets.OrderByDescending(s => s.Priority).ThenBy(s => s, new UnifiedSuggestedActionSetComparer(selectionOpt))]; } private static UnifiedSuggestedActionSet WithPriority( diff --git a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs index a0fa0af6eef3a..b33007e43a257 100644 --- a/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/CodeActions/CodeActionHelpers.cs @@ -124,12 +124,12 @@ private static LSP.CodeAction[] GenerateCodeActions( { CommandIdentifier = CodeActionsHandler.RunNestedCodeActionCommandName, Title = title, - Arguments = [new CodeActionResolveData(title, codeAction.CustomTags, request.Range, request.TextDocument, codeActionPathList.ToArray(), fixAllFlavors: null, nestedCodeActions: nestedCodeActions)] + Arguments = [new CodeActionResolveData(title, codeAction.CustomTags, request.Range, request.TextDocument, [.. codeActionPathList], fixAllFlavors: null, nestedCodeActions: nestedCodeActions)] }; } AddLSPCodeActions(builder, codeAction, request, codeActionKind, diagnosticsForFix, nestedCodeActionCommand, - nestedCodeActions, codeActionPathList.ToArray(), suggestedAction); + nestedCodeActions, [.. codeActionPathList], suggestedAction); return builder.ToArray(); } @@ -161,7 +161,7 @@ private static LSP.CodeAction[] GenerateCodeActions( if (!isTopLevelCodeAction) { AddLSPCodeActions(nestedCodeActions, codeAction, request, codeActionKind, diagnosticsForFix, - nestedCodeActionCommand: null, nestedCodeActions: null, pathOfParentAction.ToArray(), suggestedAction); + nestedCodeActionCommand: null, nestedCodeActions: null, [.. pathOfParentAction], suggestedAction); } } @@ -240,7 +240,7 @@ private static VSInternalCodeAction GenerateVSCodeAction( Priority = UnifiedSuggestedActionSetPriorityToPriorityLevel(setPriority), Group = $"Roslyn{currentSetNumber}", ApplicableRange = applicableRange, - Data = new CodeActionResolveData(codeAction.Title, codeAction.CustomTags, request.Range, request.TextDocument, fixAllFlavors: null, nestedCodeActions: null, codeActionPath: codeActionPathList.ToArray()) + Data = new CodeActionResolveData(codeAction.Title, codeAction.CustomTags, request.Range, request.TextDocument, fixAllFlavors: null, nestedCodeActions: null, codeActionPath: [.. codeActionPathList]) }; static VSInternalCodeAction[] GenerateNestedVSCodeActions( diff --git a/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs index 176c3f17b0739..806d070ff0463 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Highlights/DocumentHighlightHandler.cs @@ -53,14 +53,14 @@ public DocumentHighlightsHandler(IHighlightingService highlightingService, IGlob var keywordHighlights = await GetKeywordHighlightsAsync(document, text, position, cancellationToken).ConfigureAwait(false); if (keywordHighlights.Any()) { - return keywordHighlights.ToArray(); + return [.. keywordHighlights]; } // Not a keyword, check if it is a reference that needs highlighting. var referenceHighlights = await GetReferenceHighlightsAsync(document, text, position, cancellationToken).ConfigureAwait(false); if (referenceHighlights.Any()) { - return referenceHighlights.ToArray(); + return [.. referenceHighlights]; } // No keyword or references to highlight at this location. diff --git a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs index a488aefea3f12..7ea64d0c5d474 100644 --- a/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/OnAutoInsert/OnAutoInsertHandler.cs @@ -166,7 +166,7 @@ public OnAutoInsertHandler( var indentedText = GetIndentedText(newSourceText, caretLine, desiredCaretLinePosition, options); // Get the overall text changes between the original text and the formatted + indented text. - textChanges = indentedText.GetTextChanges(sourceText).ToImmutableArray(); + textChanges = [.. indentedText.GetTextChanges(sourceText)]; newSourceText = indentedText; // If tabs were inserted the desired caret column can remain beyond the line text. diff --git a/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs b/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs index 1fd14087e596d..ee897994efe61 100644 --- a/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs @@ -63,7 +63,7 @@ public GetTextDocumentWithContextHandler() return Task.FromResult(new VSProjectContextList { - ProjectContexts = contexts.ToArray(), + ProjectContexts = [.. contexts], DefaultIndex = documentIds.IndexOf(d => d == currentContextDocumentId) }); } diff --git a/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs b/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs index 2338d5b383c31..9e57963e23ef3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs +++ b/src/Features/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs @@ -259,7 +259,7 @@ public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem refere var docText = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var classifiedTextRuns = GetClassifiedTextRuns(_id, definitionId, documentSpan.Value, isWrittenTo, classifiedSpans, docText); - return new ClassifiedTextElement(classifiedTextRuns.ToArray()); + return new ClassifiedTextElement([.. classifiedTextRuns]); } // Certain definitions may not have a DocumentSpan, such as namespace and metadata definitions @@ -317,7 +317,7 @@ private static ClassifiedTextRun[] GetClassifiedTextRuns( private ValueTask ReportReferencesAsync(ImmutableSegmentedList> referencesToReport, CancellationToken cancellationToken) { // We can report outside of the lock here since _progress is thread-safe. - _progress.Report(referencesToReport.ToArray()); + _progress.Report([.. referencesToReport]); return ValueTaskFactory.CompletedTask; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs index 752ec12df2eeb..03c19b8338212 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensSchema.cs @@ -102,7 +102,7 @@ public SemanticTokensSchema(IReadOnlyDictionary tokenTypeMap) .Order() .ToImmutableArray(); - AllTokenTypes = SemanticTokenTypes.AllTypes.Concat(customTokenTypes).ToImmutableArray(); + AllTokenTypes = [.. SemanticTokenTypes.AllTypes, .. customTokenTypes]; var tokenTypeToIndex = new Dictionary(); diff --git a/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs b/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs index f604d0049d19f..099a38a5f3889 100644 --- a/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs +++ b/src/Features/LanguageServer/Protocol/LspServices/LspServices.cs @@ -153,7 +153,7 @@ public void Dispose() ImmutableArray disposableServices; lock (_gate) { - disposableServices = _servicesToDispose.ToImmutableArray(); + disposableServices = [.. _servicesToDispose]; _servicesToDispose.Clear(); } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs index 00dfa0293382c..b9d7c7a6dff16 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionFeaturesTests.cs @@ -390,7 +390,7 @@ void M() await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = clientCapability, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspMockCompletionService.Factory) }.ToList()); + extraExportedTypes: [typeof(CSharpLspMockCompletionService.Factory)]); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspMockCompletionService; mockService.NonDefaultRule = CompletionItemRules.Default.WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, ' ', '(')); @@ -437,7 +437,7 @@ public async Task TestUsingServerDefaultCommitCharacters(bool mutatingLspWorkspa var markup = "Item{|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspMockCompletionService.Factory) }.ToList()); + extraExportedTypes: [typeof(CSharpLspMockCompletionService.Factory)]); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspMockCompletionService; mockService.NonDefaultRule = CompletionItemRules.Default.WithCommitCharacterRule(CharacterSetModificationRule.Create(CharacterSetModificationKind.Remove, ' ', '(')); @@ -765,7 +765,7 @@ public async Task TestSoftSelectionWhenFilterTextIsEmptyForPreselectItemAsync(bo var markup = "{|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspMockCompletionService.Factory) }.ToList()); + extraExportedTypes: [typeof(CSharpLspMockCompletionService.Factory)]); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspMockCompletionService; mockService.NonDefaultRule = CompletionItemRules.Default.WithMatchPriority(MatchPriority.Preselect); @@ -873,7 +873,7 @@ public async Task TestHandleExceptionFromGetCompletionChange(bool mutatingLspWor var markup = "Item {|caret:|}"; await using var testLspServer = await CreateTestLspServerAsync(new[] { markup }, LanguageNames.CSharp, mutatingLspWorkspace, new InitializationOptions { ClientCapabilities = DefaultClientCapabilities, CallInitialized = true }, - extraExportedTypes: new[] { typeof(CSharpLspThrowExceptionOnChangeCompletionService.Factory) }.ToList()); + extraExportedTypes: [typeof(CSharpLspThrowExceptionOnChangeCompletionService.Factory)]); var mockService = testLspServer.TestWorkspace.Services.GetLanguageServices(LanguageNames.CSharp).GetRequiredService() as CSharpLspThrowExceptionOnChangeCompletionService; var builder = ImmutableArray.CreateBuilder(); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs index ed5bbf322c5e3..f4abf45dec3ce 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Symbols/DocumentSymbolsTests.cs @@ -181,7 +181,7 @@ private static LSP.DocumentSymbol CreateDocumentSymbol(LSP.SymbolKind kind, stri { var children = parent.Children.ToList(); children.Add(documentSymbol); - parent.Children = children.ToArray(); + parent.Children = [.. children]; } return documentSymbol; diff --git a/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs b/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs index a6c802e7ab9ee..3534e172218e2 100644 --- a/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs +++ b/src/Features/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs @@ -110,7 +110,7 @@ public static void Main() Assert.False(reader.TryGetDocumentChecksum("/A/C.cs", out _, out _)); Assert.True(reader.TryGetDocumentChecksum("/a/c.cs", out var actualChecksum, out var actualAlgorithm)); - Assert.Equal("21-C8-B2-D7-A3-6B-49-C7-57-DF-67-B8-1F-75-DF-6A-64-FD-59-22", BitConverter.ToString(actualChecksum.ToArray())); + Assert.Equal("21-C8-B2-D7-A3-6B-49-C7-57-DF-67-B8-1F-75-DF-6A-64-FD-59-22", BitConverter.ToString([.. actualChecksum])); Assert.Equal(new Guid("ff1816ec-aa5e-4d10-87f7-6f4963833460"), actualAlgorithm); } } diff --git a/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs b/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs index 9685f82743816..e710e3b6e2c5b 100644 --- a/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs +++ b/src/Features/TestUtilities/EditAndContinue/ActiveStatementsDescription.cs @@ -100,7 +100,7 @@ internal static ImmutableArray CreateActiveStatementMap var activeStatementMarkers = SourceMarkers.GetActiveSpans(markedSource).ToArray(); var exceptionRegionMarkers = SourceMarkers.GetExceptionRegions(markedSource); - return activeStatementMarkers.Aggregate( + return [.. activeStatementMarkers.Aggregate( new List(), (list, marker) => { @@ -126,7 +126,7 @@ internal static ImmutableArray CreateActiveStatementMap documentActiveStatements.Add(unmappedActiveStatement.Statement); return SourceMarkers.SetListItem(list, ordinal, unmappedActiveStatement); - }).ToImmutableArray(); + })]; } internal static ImmutableArray GetUnmappedActiveStatements( diff --git a/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs b/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs index 0efea7cc9ab11..1c43208de1796 100644 --- a/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs +++ b/src/Features/TestUtilities/EditAndContinue/SourceMarkers.cs @@ -106,7 +106,7 @@ public static (int id, TextSpan span)[] GetTrackingSpans(string src) Contract.ThrowIfTrue(result.Any(span => span == default)); - return result.ToArray(); + return [.. result]; } public static ImmutableArray> GetExceptionRegions(string markedSource) diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs index 8066dae54677e..0f901c2bb2478 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs @@ -44,7 +44,7 @@ public Data Serialize() => new Data() { HasGlobalAssemblyCache = HasGlobalAssemblyCache, - PlatformAssemblyPaths = PlatformAssemblyPaths.ToArray(), + PlatformAssemblyPaths = [.. PlatformAssemblyPaths], }; public static InteractiveHostPlatformInfo GetCurrentPlatformInfo() diff --git a/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs b/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs index 1fd4f2c9b7e87..7b54f0437ba74 100644 --- a/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs +++ b/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs @@ -64,8 +64,8 @@ public Data Serialize() => new Data() { Success = Success, - SourcePaths = SourcePaths.ToArray(), - ReferencePaths = ReferencePaths.ToArray(), + SourcePaths = [.. SourcePaths], + ReferencePaths = [.. ReferencePaths], WorkingDirectory = WorkingDirectory, InitializationResult = InitializationResult?.Serialize(), }; diff --git a/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs b/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs index 7dc06aaabe5b2..e1cac0b878d68 100644 --- a/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs +++ b/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs @@ -43,8 +43,8 @@ public Data Serialize() => new Data() { ScriptPath = ScriptPath, - MetadataReferencePaths = MetadataReferencePaths.ToArray(), - Imports = Imports.ToArray(), + MetadataReferencePaths = [.. MetadataReferencePaths], + Imports = [.. Imports], }; } } diff --git a/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs b/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs index c236c73222d8f..ceaeb09bbb656 100644 --- a/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs +++ b/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs @@ -532,7 +532,7 @@ public async Task MissingRefrencesAutoResolution() var portableLibRef = portableLib.ToMetadataReference(); var loader = new InteractiveAssemblyLoader(); - loader.RegisterDependency(Assembly.Load(portableLib.EmitToArray().ToArray())); + loader.RegisterDependency(Assembly.Load([.. portableLib.EmitToArray()])); var s0 = await CSharpScript.Create("new C()", options: ScriptOptions.Default.AddReferences(portableLibRef), assemblyLoader: loader).RunAsync(); var c0 = s0.Script.GetCompilation(); @@ -569,7 +569,7 @@ public void HostObjectInInMemoryAssembly() var libImage = lib.EmitToArray(); var libRef = MetadataImageReference.CreateFromImage(libImage); - var libAssembly = Assembly.Load(libImage.ToArray()); + var libAssembly = Assembly.Load([.. libImage]); var globalsType = libAssembly.GetType("C"); var globals = Activator.CreateInstance(globalsType); diff --git a/src/Scripting/Core/Hosting/SynchronizedList.cs b/src/Scripting/Core/Hosting/SynchronizedList.cs index 1b99490d96b50..a6e399afb7a69 100644 --- a/src/Scripting/Core/Hosting/SynchronizedList.cs +++ b/src/Scripting/Core/Hosting/SynchronizedList.cs @@ -83,7 +83,7 @@ public IEnumerator GetEnumerator() lock (_guard) { // make a copy to ensure thread-safe enumeration - return ((IEnumerable)_list.ToArray()).GetEnumerator(); + return ((IEnumerable)[.. _list]).GetEnumerator(); } } diff --git a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs index f20f773ac3597..3cdb0a2d003c0 100644 --- a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs +++ b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs @@ -117,10 +117,10 @@ internal TSelf Verify() } readers.Add(generation.MetadataReader); - var verifier = new GenerationVerifier(index, generation, readers.ToImmutableArray()); + var verifier = new GenerationVerifier(index, generation, [.. readers]); generation.Verifier(verifier); - exceptions.Add(verifier.Exceptions.ToImmutableArray()); + exceptions.Add([.. verifier.Exceptions]); index++; } diff --git a/src/Test/PdbUtilities/Reader/SymReaderFactory.cs b/src/Test/PdbUtilities/Reader/SymReaderFactory.cs index 8968f175f0b5a..d15171d69a9cd 100644 --- a/src/Test/PdbUtilities/Reader/SymReaderFactory.cs +++ b/src/Test/PdbUtilities/Reader/SymReaderFactory.cs @@ -72,7 +72,7 @@ public static ISymUnmanagedReader5 CreateReader(byte[] pdbImage, byte[] peImageO public static ISymUnmanagedReader5 CreateReader(ImmutableArray pdbImage, ImmutableArray peImageOpt = default(ImmutableArray)) { - return CreateReader(new MemoryStream(pdbImage.ToArray()), (peImageOpt.IsDefault) ? null : new PEReader(peImageOpt)); + return CreateReader(new MemoryStream([.. pdbImage]), (peImageOpt.IsDefault) ? null : new PEReader(peImageOpt)); } public static ISymUnmanagedReader5 CreateReader(Stream pdbStream, Stream peStreamOpt = null) diff --git a/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs b/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs index 61de9bce50d17..f5dffe99106d3 100644 --- a/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs +++ b/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs @@ -1467,7 +1467,7 @@ private static void LoadDbiStream(BitAccess bits, out DbiModuleInfo[] modules, o if (modList.Count > 0) { - modules = modList.ToArray(); + modules = [.. modList]; } else { diff --git a/src/Tools/BuildActionTelemetryTable/Program.cs b/src/Tools/BuildActionTelemetryTable/Program.cs index d03b7331e2d4e..0adb386609d4f 100644 --- a/src/Tools/BuildActionTelemetryTable/Program.cs +++ b/src/Tools/BuildActionTelemetryTable/Program.cs @@ -493,11 +493,10 @@ static bool isCodeActionProviderType(Type t) => typeof(CodeFixProvider).IsAssign internal static ImmutableArray<(string TypeName, string Hash)> GetTelemetryInfos(ImmutableArray codeActionAndProviderTypes) { - return codeActionAndProviderTypes + return [.. codeActionAndProviderTypes .Distinct(FullNameTypeComparer.Instance) .Select(GetTelemetryInfo) - .OrderBy(info => info.TypeName) - .ToImmutableArray(); + .OrderBy(info => info.TypeName)]; static (string TypeName, string Hash) GetTelemetryInfo(Type type) { diff --git a/src/Tools/BuildValidator/CompilationDiff.cs b/src/Tools/BuildValidator/CompilationDiff.cs index a8d24aca794c2..ed741dbe60ccb 100644 --- a/src/Tools/BuildValidator/CompilationDiff.cs +++ b/src/Tools/BuildValidator/CompilationDiff.cs @@ -176,7 +176,7 @@ MetadataReader getRebuildPdbReader() { if (hasEmbeddedPdb) { - var peReader = new PEReader(rebuildBytes.ToImmutableArray()); + var peReader = new PEReader([.. rebuildBytes]); return peReader.GetEmbeddedPdbMetadataReader() ?? throw ExceptionUtilities.Unreachable(); } else @@ -261,8 +261,8 @@ void writeBinaryDiffArtifacts() Debug.Assert(_rebuildPdbReader is object); Debug.Assert(_rebuildCompilation is object); - var originalPeReader = new PEReader(_originalPortableExecutableBytes.ToImmutableArray()); - var rebuildPeReader = new PEReader(_rebuildPortableExecutableBytes.ToImmutableArray()); + var originalPeReader = new PEReader([.. _originalPortableExecutableBytes]); + var rebuildPeReader = new PEReader([.. _rebuildPortableExecutableBytes]); var originalInfo = new BuildInfo( AssemblyBytes: _originalPortableExecutableBytes, AssemblyReader: originalPeReader, @@ -401,7 +401,7 @@ void writeEmbeddedFileInfo() { if (!info.CompressedHash.IsDefaultOrEmpty) { - var hashString = BitConverter.ToString(info.CompressedHash.ToArray()).Replace("-", ""); + var hashString = BitConverter.ToString([.. info.CompressedHash]).Replace("-", ""); writer.WriteLine($@"\t""{Path.GetFileName(info.SourceTextInfo.OriginalSourceFilePath)}"" - {hashString}"); } } diff --git a/src/Tools/BuildValidator/Program.cs b/src/Tools/BuildValidator/Program.cs index 7f19bb818826a..7b74aa2bb1ada 100644 --- a/src/Tools/BuildValidator/Program.cs +++ b/src/Tools/BuildValidator/Program.cs @@ -108,7 +108,7 @@ static int HandleCommand(string[] assembliesPath, string[]? exclude, string sour excludes.Add(Path.DirectorySeparatorChar + "runtimes" + Path.DirectorySeparatorChar); excludes.Add(@".resources.dll"); - var options = new Options(assembliesPath, referencesPath, excludes.ToArray(), sourcePath, verbose, quiet, debug, debugPath); + var options = new Options(assembliesPath, referencesPath, [.. excludes], sourcePath, verbose, quiet, debug, debugPath); // TODO: remove the DemoLoggerProvider or convert it to something more permanent var loggerFactory = LoggerFactory.Create(builder => @@ -221,7 +221,7 @@ private static AssemblyInfo[] GetAssemblyInfos( } } - return map.Values.OrderBy(x => x.FileName, FileNameEqualityComparer.StringComparer).ToArray(); + return [.. map.Values.OrderBy(x => x.FileName, FileNameEqualityComparer.StringComparer)]; static IEnumerable getAssemblyPaths(string directory) { diff --git a/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs b/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs index d186da23ac528..64b1a31fdfa0d 100644 --- a/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs @@ -65,7 +65,7 @@ public override async Task> MapSpansAsync( } } - return roslynSpans.ToImmutableArray(); + return [.. roslynSpans]; } } } diff --git a/src/Tools/Replay/Replay.cs b/src/Tools/Replay/Replay.cs index 444b79364ae03..2467e612b5105 100644 --- a/src/Tools/Replay/Replay.cs +++ b/src/Tools/Replay/Replay.cs @@ -196,7 +196,7 @@ static async Task BuildAsync( var request = BuildServerConnection.CreateBuildRequest( outputName, compilerCall.IsCSharp ? RequestLanguage.CSharpCompile : RequestLanguage.VisualBasicCompile, - args.ToList(), + [.. args], workingDirectory: compilerCall.ProjectDirectory, tempDirectory: options.TempDirectory, keepAlive: null, diff --git a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs index 1bfd842325f35..8c18e4f662dc1 100644 --- a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs +++ b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs @@ -106,7 +106,7 @@ internal void ExecuteImpl(IEnumerable<(string apiSpecPath, IReadOnlyList } var peImageBuffer = File.ReadAllBytes(originalReferencePath); - Rewrite(peImageBuffer, patterns.ToImmutableArray()); + Rewrite(peImageBuffer, [.. patterns]); try { @@ -302,21 +302,21 @@ internal static unsafe void Rewrite(byte[] peImage, ImmutableArray p writer, metadataReader, patterns, - types.OrderBy(t => t.MetadataToken).ToImmutableArray(), + [.. types.OrderBy(t => t.MetadataToken)], metadataOffset); UpdateMethodDefinitions( writer, metadataReader, patterns, - methods.OrderBy(t => t.MetadataToken).ToImmutableArray(), + [.. methods.OrderBy(t => t.MetadataToken)], metadataOffset); UpdateFieldDefinitions( writer, metadataReader, patterns, - fields.OrderBy(t => t.MetadataToken).ToImmutableArray(), + [.. fields.OrderBy(t => t.MetadataToken)], metadataOffset); // unsign: diff --git a/src/Tools/Source/RunTests/AssemblyScheduler.cs b/src/Tools/Source/RunTests/AssemblyScheduler.cs index 4f54d609f98a8..56eb6bb308f1f 100644 --- a/src/Tools/Source/RunTests/AssemblyScheduler.cs +++ b/src/Tools/Source/RunTests/AssemblyScheduler.cs @@ -112,7 +112,7 @@ static ImmutableArray CreateWorkItemsForFullAssemblies(ImmutableAr workItems.Add(new WorkItemInfo(currentWorkItem, partitionIndex++)); } - return workItems.ToImmutableArray(); + return [.. workItems]; } } @@ -235,7 +235,7 @@ private ImmutableArray BuildWorkItems( // Add any remaining tests to the work item. AddCurrentWorkItem(); - return workItems.ToImmutableArray(); + return [.. workItems]; void AddCurrentWorkItem() { diff --git a/src/Tools/Source/RunTests/Program.cs b/src/Tools/Source/RunTests/Program.cs index 104bdcad356bb..121cd0dc85dc9 100644 --- a/src/Tools/Source/RunTests/Program.cs +++ b/src/Tools/Source/RunTests/Program.cs @@ -351,7 +351,7 @@ private static ImmutableArray GetAssemblyFilePaths(Options options } list.Sort(); - return list.ToImmutableArray(); + return [.. list]; static bool shouldInclude(string name, Options options) { diff --git a/src/Tools/Source/RunTests/TestRunner.cs b/src/Tools/Source/RunTests/TestRunner.cs index f837f34a2e8d9..2d21960723ac5 100644 --- a/src/Tools/Source/RunTests/TestRunner.cs +++ b/src/Tools/Source/RunTests/TestRunner.cs @@ -406,7 +406,7 @@ internal async Task RunAllAsync(ImmutableArray workI processResults.AddRange(c.ProcessResults); } - return new RunAllResult((failures == 0), completed.ToImmutableArray(), processResults.ToImmutable()); + return new RunAllResult((failures == 0), [.. completed], processResults.ToImmutable()); } private void Print(List testResults) diff --git a/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs b/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs index 450ff38f04e2f..1453ad36fe7dc 100644 --- a/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs +++ b/src/VisualStudio/CSharp/Impl/ChangeSignature/CSharpChangeSignatureViewModelFactoryService.cs @@ -47,7 +47,7 @@ public override SymbolDisplayPart[] GeneratePreviewDisplayParts(AddedParameterVi parts.Add(new SymbolDisplayPart(SymbolDisplayPartKind.Text, null, addedParameterViewModel.Default)); } - return parts.ToArray(); + return [.. parts]; } // Use LangVersion Preview to ensure that all types parse correctly. If the user types in a type only available diff --git a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs index 6544889756b73..c5481121e2963 100644 --- a/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs +++ b/src/VisualStudio/Core/Def/EditorConfigSettings/SettingsEditorControl.xaml.cs @@ -66,7 +66,7 @@ public SettingsEditorControl(ISettingsEditorView whitespaceView, analyzerView ]; - _tableControls = _views.SelectAsArray(view => view.TableControl).ToArray(); + _tableControls = [.. _views.SelectAsArray(view => view.TableControl)]; InitializeComponent(); } diff --git a/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs b/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs index 9e5a817ba411b..49794d90e20f2 100644 --- a/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs +++ b/src/VisualStudio/Core/Def/ErrorReporting/VisualStudioErrorReportingService.cs @@ -75,7 +75,7 @@ public void ShowFeatureNotAvailableErrorInfo(string message, TelemetryFeatureNam closeAfterAction: true)); } - ShowGlobalErrorInfo(message, featureName, exception, infoBarUIs.ToArray()); + ShowGlobalErrorInfo(message, featureName, exception, [.. infoBarUIs]); } private void LogGlobalErrorToActivityLog(string message, string? detailedError) diff --git a/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs b/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs index 2cc9eb4b5c06b..a2a015311f028 100644 --- a/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs +++ b/src/VisualStudio/Core/Def/Implementation/VsRefactorNotifyService.cs @@ -46,7 +46,7 @@ public bool TryOnBeforeGlobalSymbolRenamed(Workspace workspace, IEnumerable GetPartialForNamedTypeAsync(INamedTypeSym partials.Add(GraphNodeId.GetArray( CodeGraphNodeIdName.GenericArgumentsIdentifier, - genericArguments.ToArray())); + [.. genericArguments])); } if (namedType.ContainingType != null) @@ -206,7 +206,7 @@ private static async Task GetPartialForNamedTypeAsync(INamedTypeSym partials.Add(await GetPartialForTypeAsync(namedType.ContainingType, CodeGraphNodeIdName.ParentType, solution, cancellationToken, hasGenericArguments).ConfigureAwait(false)); } - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary(partials.ToArray())); + return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); } } @@ -231,7 +231,7 @@ private static async Task GetPartialForPointerTypeAsync(IPointerTyp partials.Add(await GetPartialForTypeAsync(pointerType.PointedAtType.ContainingType, CodeGraphNodeIdName.ParentType, solution, cancellationToken).ConfigureAwait(false)); } - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary(partials.ToArray())); + return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); } private static async Task GetPartialForArrayTypeAsync(IArrayTypeSymbol arrayType, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken) @@ -252,7 +252,7 @@ private static async Task GetPartialForArrayTypeAsync(IArrayTypeSym partials.Add(GraphNodeId.GetPartial(CodeQualifiedName.ArrayRank, arrayType.Rank.ToString())); partials.Add(await GetPartialForTypeAsync(arrayType.ElementType, CodeGraphNodeIdName.ParentType, solution, cancellationToken).ConfigureAwait(false)); - return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary(partials.ToArray())); + return GraphNodeId.GetPartial(nodeName, MakeCollectionIfNecessary([.. partials])); } private static async Task GetPartialForTypeParameterSymbolAsync(ITypeParameterSymbol typeParameterSymbol, GraphNodeIdName nodeName, Solution solution, CancellationToken cancellationToken) @@ -317,7 +317,7 @@ public static async Task GetIdForMemberAsync(ISymbol member, Soluti nodes.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.ParamKind, ParamKind.Ref)); } - parameterTypeIds.Add(GraphNodeId.GetNested(nodes.ToArray())); + parameterTypeIds.Add(GraphNodeId.GetNested([.. nodes])); } if (member is IMethodSymbol methodSymbol && methodSymbol.MethodKind == MethodKind.Conversion) @@ -336,25 +336,25 @@ public static async Task GetIdForMemberAsync(ISymbol member, Soluti var returnTypePartial = nodes.ToList(); returnTypePartial.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.ParamKind, Microsoft.VisualStudio.GraphModel.CodeSchema.ParamKind.Return)); - var returnCollection = GraphNodeId.GetNested(returnTypePartial.ToArray()); + var returnCollection = GraphNodeId.GetNested([.. returnTypePartial]); parameterTypeIds.Add(returnCollection); } memberPartials.Add(GraphNodeId.GetArray( CodeGraphNodeIdName.OverloadingParameters, - parameterTypeIds.ToArray())); + [.. parameterTypeIds])); } partials.Add(GraphNodeId.GetPartial( CodeGraphNodeIdName.Member, - MakeCollectionIfNecessary(memberPartials.ToArray()))); + MakeCollectionIfNecessary([.. memberPartials]))); } else { partials.Add(GraphNodeId.GetPartial(CodeGraphNodeIdName.Member, member.MetadataName)); } - return GraphNodeId.GetNested(partials.ToArray()); + return GraphNodeId.GetNested([.. partials]); } private static object MakeCollectionIfNecessary(GraphNodeId[] array) diff --git a/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs b/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs index ace4d7cfc5ea6..e15e7d56ecbc2 100644 --- a/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs +++ b/src/VisualStudio/Core/Def/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs @@ -40,7 +40,7 @@ public IWpfTableControl4 CreateTableControl() _tableManager, autoSubscribe: true, BuildColumnStates(), - UnusedReferencesColumnDefinitions.ColumnNames.ToArray()); + [.. UnusedReferencesColumnDefinitions.ColumnNames]); tableControl.ShowGroupingLine = true; tableControl.DoColumnsAutoAdjust = true; tableControl.DoSortingAndGroupingWhileUnstable = true; diff --git a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs index 3a1213d0d2013..4a980f4e05115 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/FileCodeModel_CodeGen.cs @@ -141,7 +141,7 @@ private static object[] GetValidArray(object itemOrArray, bool allowMultipleElem } } - return result.ToArray(); + return [.. result]; } internal EnvDTE80.CodeAttributeArgument AddAttributeArgument(SyntaxNode containerNode, string name, string value, object position) diff --git a/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs b/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs index 9ca3dba4d904b..94ff8208e4117 100644 --- a/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs +++ b/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs @@ -148,7 +148,7 @@ public void UpdatePreview(string text) _editorOptions.CreateOptions(), textBuffer.CurrentSnapshot, separator: "", - exposedLineSpans: GetExposedLineSpans(textBuffer.CurrentSnapshot).ToArray()); + exposedLineSpans: [.. GetExposedLineSpans(textBuffer.CurrentSnapshot)]); var textView = _textEditorFactoryService.CreateTextView(projection, _textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Interactive)); @@ -224,7 +224,7 @@ protected void AddParenthesesOption( isChecked: !defaultAddForClarity)); CodeStyleItems.Add(new EnumCodeStyleOptionViewModel( - languageOption, title, preferences.ToArray(), + languageOption, title, [.. preferences], examples, this, optionStore, ServicesVSResources.Parentheses_preferences_colon, codeStylePreferences)); } diff --git a/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs b/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs index 983d1474bfea5..75f2401bcaaf0 100644 --- a/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs +++ b/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageControl.xaml.cs @@ -72,7 +72,7 @@ private void AddButton_Click(object sender, RoutedEventArgs e) private void ManageSpecificationsButton_Click(object sender, RoutedEventArgs e) { - var viewModel = new ManageSymbolSpecificationsDialogViewModel(_viewModel.Specifications, _viewModel.CodeStyleItems.ToList(), _languageName, _notificationService); + var viewModel = new ManageSymbolSpecificationsDialogViewModel(_viewModel.Specifications, [.. _viewModel.CodeStyleItems], _languageName, _notificationService); var dialog = new ManageNamingStylesInfoDialog(viewModel); if (dialog.ShowModal().Value == true) { @@ -82,7 +82,7 @@ private void ManageSpecificationsButton_Click(object sender, RoutedEventArgs e) private void ManageStylesButton_Click(object sender, RoutedEventArgs e) { - var viewModel = new ManageNamingStylesDialogViewModel(_viewModel.NamingStyles, _viewModel.CodeStyleItems.ToList(), _notificationService); + var viewModel = new ManageNamingStylesDialogViewModel(_viewModel.NamingStyles, [.. _viewModel.CodeStyleItems], _notificationService); var dialog = new ManageNamingStylesInfoDialog(viewModel); if (dialog.ShowModal().Value == true) { diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 45fdb1c714ec7..50107b9f10764 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -1525,7 +1525,7 @@ private static Solution Populate(Solution solution) ], [ "cs additional file content" - ], solution.ProjectIds.ToArray()); + ], [.. solution.ProjectIds]); solution = AddProject(solution, LanguageNames.CSharp, [ diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs index 8af34dd060cae..d3da6ef64f484 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/EditorInProcess.cs @@ -309,7 +309,7 @@ public async Task GetLightBulbPreviewClassificationsAsync( activeSession.Collapse(); var classifier = classifierAggregatorService.GetClassifier(preview); var classifiedSpans = classifier.GetClassificationSpans(new SnapshotSpan(preview.TextBuffer.CurrentSnapshot, 0, preview.TextBuffer.CurrentSnapshot.Length)); - return classifiedSpans.ToArray(); + return [.. classifiedSpans]; } activeSession.Collapse(); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs index 517c21d606ad1..c9ca70e510dc0 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/ScreenshotInProcess.cs @@ -157,7 +157,7 @@ static ScreenshotInProcess() return; } - frames = s_frames.ToArray(); + frames = [.. s_frames]; } // Make sure the frames are processed in order of their timestamps @@ -303,7 +303,7 @@ private static (TimeSpan elapsed, BitmapSource image, Size offset)[] DetectChang Marshal.FreeHGlobal(imageBuffer); } - return resultFrames.ToArray(); + return [.. resultFrames]; } private static void WritePngSignature(Stream stream, byte[] buffer) diff --git a/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs b/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs index e922257ddd994..38c1fb7179dc0 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/LanguageServiceUtils.cs @@ -23,7 +23,7 @@ public static string GetLanguageServerProviderServiceName(string[] contentTypes) public static string GetLanguageServerProviderServiceName(string lspServiceName) => LanguageServerProviderServiceName + "-" + lspServiceName; - public static string GetContentTypesName(string[] contentTypes) => string.Join("-", contentTypes.OrderBy(c => c).ToArray()); + public static string GetContentTypesName(string[] contentTypes) => string.Join("-", [.. contentTypes.OrderBy(c => c)]); public static bool IsContentTypeRemote(string contentType) => contentType.EndsWith("-remote"); diff --git a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs index 38fd06245c515..e949ef523ae1c 100644 --- a/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs +++ b/src/VisualStudio/VisualStudioDiagnosticsToolWindow/Panels/WorkspacePanel.xaml.cs @@ -99,7 +99,7 @@ async Task CompareDocumentAsync(Document document) { lock (gate) { - output.AppendLine($"{document.FilePath}: {BitConverter.ToString(snapshotChecksum.ToArray())} : {BitConverter.ToString(fileChecksum.ToArray())}"); + output.AppendLine($"{document.FilePath}: {BitConverter.ToString([.. snapshotChecksum])} : {BitConverter.ToString([.. fileChecksum])}"); outOfDateCount++; } } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs index e7c14eb07a3eb..a1ee575ad5aed 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs @@ -55,13 +55,13 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe var document = context.Document; if (document == null) { - return locations.ToArray(); + return [.. locations]; } var xamlGoToDefinitionService = document.Project.Services.GetService(); if (xamlGoToDefinitionService == null) { - return locations.ToArray(); + return [.. locations]; } var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); @@ -83,7 +83,7 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe await Task.WhenAll(tasks).ConfigureAwait(false); - return locations.ToArray(); + return [.. locations]; } private async Task GetLocationsAsync(XamlDefinition definition, RequestContext context, CancellationToken cancellationToken) diff --git a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs b/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs index c45a418d8c073..5df82749fae48 100644 --- a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs +++ b/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs @@ -148,7 +148,7 @@ public ImmutableArray GetFormattingChangesOnTypedCharacter( return changes; } - return FormatToken(document, indentationOptions, token, formattingRules, cancellationToken).ToImmutableArray(); + return [.. FormatToken(document, indentationOptions, token, formattingRules, cancellationToken)]; } private static bool OnlySmartIndentCloseBrace(in AutoFormattingOptions options) @@ -199,7 +199,7 @@ private static ImmutableArray FormatRange( var formatter = new CSharpSmartTokenFormatter(options, formattingRules, (CompilationUnitSyntax)document.Root, document.Text); var changes = formatter.FormatRange(tokenRange.Value.Item1, tokenRange.Value.Item2, cancellationToken); - return changes.ToImmutableArray(); + return [.. changes]; } private static IEnumerable GetTypingRules(SyntaxToken tokenBeforeCaret) @@ -318,9 +318,12 @@ or SyntaxKind.EndOfDirectiveToken private ImmutableArray GetFormattingRules(ParsedDocument document, int position, SyntaxToken tokenBeforeCaret) { var formattingRuleFactory = _services.SolutionServices.GetRequiredService(); - return ImmutableArray.Create(formattingRuleFactory.CreateRule(document, position)) - .AddRange(GetTypingRules(tokenBeforeCaret)) - .AddRange(Formatter.GetDefaultFormattingRules(_services)); + return + [ + formattingRuleFactory.CreateRule(document, position), + .. GetTypingRules(tokenBeforeCaret), + .. Formatter.GetDefaultFormattingRules(_services), + ]; } public ImmutableArray GetFormattingChangesOnPaste(ParsedDocument document, TextSpan textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken) @@ -332,7 +335,7 @@ public ImmutableArray GetFormattingChangesOnPaste(ParsedDocument doc rules.AddRange(service.GetDefaultFormattingRules()); var result = service.GetFormattingResult(document.Root, [formattingSpan], options, rules, cancellationToken); - return result.GetTextChanges(cancellationToken).ToImmutableArray(); + return [.. result.GetTextChanges(cancellationToken)]; } internal sealed class PasteFormattingRule : AbstractFormattingRule diff --git a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs index 127cfd73e6a56..55bdb955984c6 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.NodesAndTokensToReduceComputer.cs @@ -31,7 +31,7 @@ public static ImmutableArray Compute(SyntaxNode root, Func< { var reduceNodeComputer = new NodesAndTokensToReduceComputer(isNodeOrTokenOutsideSimplifySpans); reduceNodeComputer.Visit(root); - return reduceNodeComputer._nodesAndTokensToReduce.ToImmutableArray(); + return [.. reduceNodeComputer._nodesAndTokensToReduce]; } private NodesAndTokensToReduceComputer(Func isNodeOrTokenOutsideSimplifySpans) diff --git a/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs b/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs index 9a331882f074f..0cba06c47234c 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/Build/ProjectBuildManager.cs @@ -260,7 +260,7 @@ public void EndBatchBuild() _batchBuildLogger?.SetProjectAndLog(projectInstance.FullPath, log); - var buildRequestData = new MSB.Execution.BuildRequestData(projectInstance, targets.ToArray()); + var buildRequestData = new MSB.Execution.BuildRequestData(projectInstance, [.. targets]); var result = await BuildAsync(buildRequestData, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs index ef9950a71ca1b..0dcc41acbffff 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/Extensions.cs @@ -48,7 +48,7 @@ public static ImmutableArray GetPackageReferences(this MSB.Exe references.Add(packageReference); } - return references.ToImmutableArray(); + return [.. references]; } /// diff --git a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs index 53deba14cf6e3..e358758782d98 100644 --- a/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs +++ b/src/Workspaces/Core/MSBuild.BuildHost/MSBuild/ProjectFile/ProjectFile.cs @@ -39,7 +39,7 @@ protected ProjectFile(ProjectFileLoader loader, MSB.Evaluation.Project? loadedPr Log = log; } - public ImmutableArray GetDiagnosticLogItems() => Log.ToImmutableArray(); + public ImmutableArray GetDiagnosticLogItems() => [.. Log]; protected abstract SourceCodeKind GetSourceCodeKind(string documentFileName); public abstract string GetDocumentExtension(SourceCodeKind kind); @@ -252,7 +252,7 @@ private ImmutableArray GetRelativeFolders(MSB.Framework.ITaskItem docume var linkPath = documentItem.GetMetadata(MetadataNames.Link); if (!RoslynString.IsNullOrEmpty(linkPath)) { - return PathUtilities.GetDirectoryName(linkPath).Split(PathUtilities.DirectorySeparatorChar, PathUtilities.AltDirectorySeparatorChar).ToImmutableArray(); + return [.. PathUtilities.GetDirectoryName(linkPath).Split(PathUtilities.DirectorySeparatorChar, PathUtilities.AltDirectorySeparatorChar)]; } else { diff --git a/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs b/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs index e50bf7d57852b..0bdbcd428bba0 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/BuildHostProcessManager.cs @@ -149,7 +149,7 @@ public async ValueTask DisposeAsync() // may try to mutate the list while we're enumerating. using (await _gate.DisposableWaitAsync().ConfigureAwait(false)) { - processesToDispose = _processes.Values.ToList(); + processesToDispose = [.. _processes.Values]; _processes.Clear(); } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs index d22ddc0d0a33e..c5c2b09fd0eb2 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs @@ -457,7 +457,7 @@ protected override void ApplyDocumentAdded(DocumentInfo info, SourceText text) var fileName = Path.ChangeExtension(info.Name, extension); var relativePath = (info.Folders != null && info.Folders.Count > 0) - ? Path.Combine(Path.Combine(info.Folders.ToArray()), fileName) + ? Path.Combine(Path.Combine([.. info.Folders]), fileName) : fileName; var fullPath = GetAbsolutePath(relativePath, Path.GetDirectoryName(project.FilePath)!); diff --git a/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs b/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs index f8178c6c5933f..a40000b3ff50b 100644 --- a/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs +++ b/src/Workspaces/Core/Portable/CaseCorrection/AbstractCaseCorrectionService.cs @@ -52,7 +52,7 @@ private SyntaxNode CaseCorrect(SemanticModel? semanticModel, SyntaxNode root, Im using (Logger.LogBlock(FunctionId.CaseCorrection_AddReplacements, cancellationToken)) { - AddReplacements(semanticModel, root, normalizedSpanCollection.ToImmutableArray(), replacements, cancellationToken); + AddReplacements(semanticModel, root, [.. normalizedSpanCollection], replacements, cancellationToken); } using (Logger.LogBlock(FunctionId.CaseCorrection_ReplaceTokens, cancellationToken)) diff --git a/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs b/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs index 1d45eb4e6ab7b..dd8e71e8f7906 100644 --- a/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs +++ b/src/Workspaces/Core/Portable/Classification/ClassifierHelper.cs @@ -139,7 +139,7 @@ private static ImmutableArray MergeClassifiedSpans( // be gaps in what it produces. Fill in those gaps so we have *all* parts of the span classified properly. using var _2 = Classifier.GetPooledList(out var filledInSpans); FillInClassifiedSpanGaps(widenedSpan.Start, mergedSpans, filledInSpans); - return filledInSpans.ToImmutableArray(); + return [.. filledInSpans]; } private static readonly Comparison s_spanComparison = static (s1, s2) => s1.TextSpan.Start - s2.TextSpan.Start; diff --git a/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs b/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs index a7bec9d9175e8..2a65deb55737d 100644 --- a/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs +++ b/src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs @@ -530,7 +530,7 @@ private ImmutableArray GetSpans( // Remove the spans we should not touch from the requested spans and return that final set. var result = NormalizedTextSpanCollection.Difference(requestedSpans, spansToAvoid); - return result.ToImmutableArray(); + return [.. result]; } private async Task IterateAllCodeCleanupProvidersAsync( @@ -594,7 +594,7 @@ private string GetCodeCleanerTypeName(ICodeCleanupProvider codeCleaner) private static SyntaxNode InjectAnnotations(SyntaxNode node, Dictionary> map) { var tokenMap = map.ToDictionary(p => p.Key, p => p.Value); - return node.ReplaceTokens(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations(tokenMap[o].ToArray())); + return node.ReplaceTokens(tokenMap.Keys, (o, n) => o.WithAdditionalAnnotations([.. tokenMap[o]])); } private static bool TryCreateTextSpan(int start, int end, out TextSpan span) diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs index 680b935e03483..883c28be7be1a 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResultBuilder.cs @@ -30,11 +30,11 @@ internal struct DiagnosticAnalysisResultBuilder(Project project, VersionStamp ve private List? _lazyOthers = null; - public readonly ImmutableHashSet DocumentIds => _lazyDocumentsWithDiagnostics == null ? [] : _lazyDocumentsWithDiagnostics.ToImmutableHashSet(); + public readonly ImmutableHashSet DocumentIds => _lazyDocumentsWithDiagnostics == null ? [] : [.. _lazyDocumentsWithDiagnostics]; public readonly ImmutableDictionary> SyntaxLocals => Convert(_lazySyntaxLocals); public readonly ImmutableDictionary> SemanticLocals => Convert(_lazySemanticLocals); public readonly ImmutableDictionary> NonLocals => Convert(_lazyNonLocals); - public readonly ImmutableArray Others => _lazyOthers == null ? [] : _lazyOthers.ToImmutableArray(); + public readonly ImmutableArray Others => _lazyOthers == null ? [] : [.. _lazyOthers]; public void AddExternalSyntaxDiagnostics(DocumentId documentId, IEnumerable diagnostics) { diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index 942a846fa8671..02950b6b50ef3 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -404,7 +404,7 @@ private static async Task> GetPragmaSuppressionAnalyz } await Task.WhenAll(tasks).ConfigureAwait(false); - return bag.ToImmutableArray(); + return [.. bag]; } else { diff --git a/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs b/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs index 555dfa7b6826f..6615ecbd563fe 100644 --- a/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs +++ b/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs @@ -229,7 +229,7 @@ private async Task AddImportDirectivesFromSymbolAnnotationsAsync( var importContainer = addImportsService.GetImportContainer(root, context, importToSyntax.First().Value, options); // Now remove any imports we think can cause conflicts in that container. - var safeImportsToAdd = GetSafeToAddImports(importToSyntax.Keys.ToImmutableArray(), importContainer, model, cancellationToken); + var safeImportsToAdd = GetSafeToAddImports([.. importToSyntax.Keys], importContainer, model, cancellationToken); var importsToAdd = importToSyntax.Where(kvp => safeImportsToAdd.Contains(kvp.Key)).Select(kvp => kvp.Value).ToImmutableArray(); if (importsToAdd.Length == 0) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs index b2a14157d4adc..84aeec57d7ada 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentProjectsFinder.cs @@ -35,7 +35,7 @@ public static async Task> GetDependentProjectsAsync( { // namespaces are visible in all projects. if (symbols.Any(static s => s.Kind == SymbolKind.Namespace)) - return projects.ToImmutableArray(); + return [.. projects]; var dependentProjects = await GetDependentProjectsWorkerAsync(solution, symbols, cancellationToken).ConfigureAwait(false); return dependentProjects.WhereAsArray(projects.Contains); @@ -84,7 +84,7 @@ private static async Task> GetDependentProjectsWorkerAsy result.AddRange(filteredProjects.Select(p => p.project)); } - return result.ToImmutableArray(); + return [.. result]; } /// @@ -145,7 +145,7 @@ private static async Task> GetDependentProjectsWorkerAsy // further submissions can bind to them. await AddSubmissionDependentProjectsAsync(solution, symbolOrigination.sourceProject, dependentProjects, cancellationToken).ConfigureAwait(false); - return dependentProjects.ToImmutableArray(); + return [.. dependentProjects]; } private static async Task AddSubmissionDependentProjectsAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 79f7ef5d7893a..9e360bd3f47cb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -136,7 +136,7 @@ private static async Task> DescendInheritanceTr await DescendInheritanceTreeInProjectAsync(project).ConfigureAwait(false); } - return result.ToImmutableArray(); + return [.. result]; async Task DescendInheritanceTreeInProjectAsync(Project project) { @@ -472,7 +472,7 @@ private static ImmutableArray OrderTopologically( index++; } - return projectsToExamine.OrderBy((p1, p2) => order[p1.Id] - order[p2.Id]).ToImmutableArray(); + return [.. projectsToExamine.OrderBy((p1, p2) => order[p1.Id] - order[p2.Id])]; } private static ImmutableArray GetProjectsToExamineWorker( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs index a8ae4b1571f97..66379d0db9d32 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.BidirectionalSymbolSet.cs @@ -42,7 +42,7 @@ public BidirectionalSymbolSet( } public override ImmutableArray GetAllSymbols() - => _allSymbols.ToImmutableArray(); + => [.. _allSymbols]; public override async Task InheritanceCascadeAsync(Project project, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs index a6778eb0608a7..2463a1d7033dc 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.NonCascadingSymbolSet.cs @@ -16,7 +16,7 @@ internal partial class FindReferencesSearchEngine /// private sealed class NonCascadingSymbolSet(FindReferencesSearchEngine engine, MetadataUnifyingSymbolHashSet searchSymbols) : SymbolSet(engine) { - private readonly ImmutableArray _symbols = searchSymbols.ToImmutableArray(); + private readonly ImmutableArray _symbols = [.. searchSymbols]; public override ImmutableArray GetAllSymbols() => _symbols; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs index 2e8ac46d238ac..f225f63350c2d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.UnidirectionalSymbolSet.cs @@ -40,7 +40,7 @@ public override ImmutableArray GetAllSymbols() var result = new MetadataUnifyingSymbolHashSet(); result.AddRange(_upSymbols); result.AddRange(initialSymbols); - return result.ToImmutableArray(); + return [.. result]; } public override async Task InheritanceCascadeAsync(Project project, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 7383f6e71739a..2b74a456075d8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -93,7 +93,7 @@ protected static async Task> FindDocumentsAsync( { var document = scope.First(); if (document.Project == project) - return scope.ToImmutableArray(); + return [.. scope]; return []; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index dfc42e1bf5983..a8e642c4330c7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -42,7 +42,7 @@ public ImmutableArray GetReferencedSymbols() { using var _ = ArrayBuilder.GetInstance(out var result); foreach (var (symbol, locations) in _symbolToLocations) - result.Add(new ReferencedSymbol(symbol, locations.ToImmutableArray())); + result.Add(new ReferencedSymbol(symbol, [.. locations])); return result.ToImmutable(); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 89be7cdadb127..50b7dd3af8c58 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -67,7 +67,7 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated map[symbolAndProjectId] = symbol; } - var symbolGroup = new SymbolGroup(map.Values.ToImmutableArray()); + var symbolGroup = new SymbolGroup([.. map.Values]); lock (_gate) { _groupMap[dehydrated] = symbolGroup; diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs index f69e50e9a3529..89ac7f1794849 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.cs @@ -308,6 +308,6 @@ internal static async Task> FindLinkedSymbolsAsync( } } - return linkedSymbols.ToImmutableArray(); + return [.. linkedSymbols]; } } diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs index 6603464b13a8e..1f7fb23a853f3 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs @@ -83,7 +83,7 @@ private async Task MergeLinkedDocumentGroupAsync( appliedChanges = await AddDocumentMergeChangesAsync( oldSolution.GetDocument(documentId), newSolution.GetDocument(documentId), - appliedChanges.ToList(), + [.. appliedChanges], unmergedChanges, groupSessionInfo, textDifferencingService, diff --git a/src/Workspaces/Core/Portable/Log/AggregateLogger.cs b/src/Workspaces/Core/Portable/Log/AggregateLogger.cs index 19a74f1a4972d..6f20609433576 100644 --- a/src/Workspaces/Core/Portable/Log/AggregateLogger.cs +++ b/src/Workspaces/Core/Portable/Log/AggregateLogger.cs @@ -36,7 +36,7 @@ public static AggregateLogger Create(params ILogger[] loggers) set.Add(logger); } - return new AggregateLogger(set.ToImmutableArray()); + return new AggregateLogger([.. set]); } public static ILogger AddOrReplace(ILogger newLogger, ILogger oldLogger, Func predicate) @@ -81,7 +81,7 @@ public static ILogger AddOrReplace(ILogger newLogger, ILogger oldLogger, Func predicate) @@ -105,7 +105,7 @@ public static ILogger Remove(ILogger logger, Func predicate) return set.Single(); } - return new AggregateLogger(set.ToImmutableArray()); + return new AggregateLogger([.. set]); } private AggregateLogger(ImmutableArray loggers) diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs index 34190d5261e2c..0a0b5e3c63411 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs @@ -177,10 +177,13 @@ public async Task ResolveConflictsAsync() if (phase == 1) { - conflictLocations = conflictLocations.Concat(conflictResolution.RelatedLocations - .Where(loc => documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict) - .Select(loc => new ConflictLocationInfo(loc))) - .ToImmutableHashSet(); + conflictLocations = + [ + .. conflictLocations, + .. conflictResolution.RelatedLocations + .Where(loc => documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict) + .Select(loc => new ConflictLocationInfo(loc)), + ]; } // Set the documents with conflicts that need to be processed in the next phase. diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs index 6c6546e908b97..7a71e5dd2b9a3 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/RenamedSpansTracker.cs @@ -226,7 +226,7 @@ internal async Task SimplifyAsync(Solution solution, IEnumerable>(); foreach (var (docId, spans) in _documentToModifiedSpansMap) - builder.Add(docId, spans.ToImmutableArray()); + builder.Add(docId, [.. spans]); return builder.ToImmutable(); } @@ -238,7 +238,7 @@ public ImmutableDictionary> GetDocu foreach (var (docId, spans) in _documentToComplexifiedSpansMap) { builder.Add(docId, spans.SelectAsArray( - s => new ComplexifiedSpan(s.OriginalSpan, s.NewSpan, s.ModifiedSubSpans.ToImmutableArray()))); + s => new ComplexifiedSpan(s.OriginalSpan, s.NewSpan, [.. s.ModifiedSubSpans]))); } return builder.ToImmutable(); diff --git a/src/Workspaces/Core/Portable/Rename/Renamer.cs b/src/Workspaces/Core/Portable/Rename/Renamer.cs index 741bab2bd9cfa..cc3c90299437b 100644 --- a/src/Workspaces/Core/Portable/Rename/Renamer.cs +++ b/src/Workspaces/Core/Portable/Rename/Renamer.cs @@ -119,7 +119,7 @@ internal static async Task RenameDocumentAsync( if (document.Services.GetService() != null) { // Don't advertise that we can file rename generated documents that map to a different file. - return new RenameDocumentActionSet([], document.Id, document.Name, document.Folders.ToImmutableArray(), options); + return new RenameDocumentActionSet([], document.Id, document.Name, [.. document.Folders], options); } using var _ = ArrayBuilder.GetInstance(out var actions); @@ -143,7 +143,7 @@ internal static async Task RenameDocumentAsync( actions.ToImmutable(), document.Id, newDocumentName, - newDocumentFolders.ToImmutableArray(), + [.. newDocumentFolders], options); } diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs index bf841432cddf8..aba239ec1cb92 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ISymbolExtensions.cs @@ -344,7 +344,7 @@ private static XNode[] RewriteMany(ISymbol symbol, HashSet? visitedSymb result.AddRange(RewriteInheritdocElements(symbol, visitedSymbols, compilation, child, cancellationToken)); } - return result.ToArray(); + return [.. result]; } private static XNode[]? RewriteInheritdocElement(ISymbol memberSymbol, HashSet? visitedSymbols, Compilation compilation, XElement element, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs index b45c69cfd0dc4..f929abc39b74d 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.AnonymousTypeRemover.cs @@ -47,7 +47,7 @@ public override ITypeSymbol VisitNamedType(INamedTypeSymbol symbol) if (arguments.SequenceEqual(symbol.TypeArguments)) return symbol; - return symbol.ConstructedFrom.Construct(arguments.ToArray()); + return symbol.ConstructedFrom.Construct([.. arguments]); } public override ITypeSymbol VisitPointerType(IPointerTypeSymbol symbol) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs index 24f728ff9b703..b591d1c9d2818 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnavailableTypeParameterRemover.cs @@ -45,7 +45,7 @@ public override ITypeSymbol VisitNamedType(INamedTypeSymbol symbol) return symbol; } - return symbol.ConstructedFrom.Construct(arguments.ToArray()); + return symbol.ConstructedFrom.Construct([.. arguments]); } public override ITypeSymbol VisitPointerType(IPointerTypeSymbol symbol) diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs index 31510f8004fae..93bde82d583ef 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ITypeSymbolExtensions.UnnamedErrorTypeRemover.cs @@ -49,7 +49,7 @@ public override ITypeSymbol VisitNamedType(INamedTypeSymbol symbol) return symbol; } - return symbol.ConstructedFrom.Construct(arguments.ToArray()); + return symbol.ConstructedFrom.Construct([.. arguments]); } public override ITypeSymbol VisitPointerType(IPointerTypeSymbol symbol) diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs index f9de85f7493c1..174d252614bbc 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListener.cs @@ -235,7 +235,7 @@ public ImmutableArray ActiveDiagnosticTokens return []; } - return _diagnosticTokenList.ToImmutableArray(); + return [.. _diagnosticTokenList]; } } } diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs index 2fb062340a4c3..ed5be74296ee3 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/AsynchronousOperationListenerProvider.cs @@ -151,7 +151,7 @@ public async Task WaitAllAsync(Workspace? workspace, string[]? featureNames = nu do { // wait for all current tasks to be done for the time given - if (Task.WaitAll(tasks.ToArray(), smallTimeout)) + if (Task.WaitAll([.. tasks], smallTimeout)) { // current set of tasks are done. // see whether there are new tasks added while we were waiting diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs index e73c12d226a0f..823d3ae7e5cca 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorageConfiguration.cs @@ -37,7 +37,7 @@ internal sealed class DefaultPersistentStorageConfiguration : IPersistentStorage /// path. For example, Base64 encoding will use / which is something that we definitely do not want /// errantly added to a path. /// - private static readonly ImmutableArray s_invalidPathChars = Path.GetInvalidPathChars().Concat('/').ToImmutableArray(); + private static readonly ImmutableArray s_invalidPathChars = [.. Path.GetInvalidPathChars(), '/']; private static readonly string s_cacheDirectory; private static readonly string s_moduleFileName; diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs index 04ba30d41afd4..0f6ed5627c13a 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.BatchingDocumentCollection.cs @@ -557,7 +557,7 @@ internal void UpdateSolutionForBatch( ClearAndZeroCapacity(_documentsAddedInBatch); // Document removing... - solutionChanges.UpdateSolutionForRemovedDocumentAction(removeDocuments(solutionChanges.Solution, _documentsRemovedInBatch.ToImmutableArray()), + solutionChanges.UpdateSolutionForRemovedDocumentAction(removeDocuments(solutionChanges.Solution, [.. _documentsRemovedInBatch]), removeDocumentChangeKind, _documentsRemovedInBatch); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs index 80fcf4203170b..a6b39decd2bb0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph.cs @@ -270,7 +270,7 @@ private ImmutableHashSet GetProjectsThatThisProjectTransitivelyDepend using var pooledObject = SharedPools.Default>().GetPooledObject(); var results = pooledObject.Object; this.ComputeTransitiveReferences(projectId, results); - transitiveReferences = results.ToImmutableHashSet(); + transitiveReferences = [.. results]; _transitiveReferencesMap = _transitiveReferencesMap.Add(projectId, transitiveReferences); } @@ -323,7 +323,7 @@ private ImmutableHashSet GetProjectsThatTransitivelyDependOnThisProje var results = pooledObject.Object; ComputeReverseTransitiveReferences(projectId, results); - reverseTransitiveReferences = results.ToImmutableHashSet(); + reverseTransitiveReferences = [.. results]; _reverseTransitiveReferencesMap = _reverseTransitiveReferencesMap.Add(projectId, reverseTransitiveReferences); } @@ -367,7 +367,7 @@ private void GetTopologicallySortedProjects_NoLock(CancellationToken cancellatio using var seenProjects = SharedPools.Default>().GetPooledObject(); using var resultList = SharedPools.Default>().GetPooledObject(); this.TopologicalSort(_projectIds, seenProjects.Object, resultList.Object, cancellationToken); - _lazyTopologicallySortedProjects = resultList.Object.ToImmutableArray(); + _lazyTopologicallySortedProjects = [.. resultList.Object]; } } @@ -419,7 +419,7 @@ private ImmutableArray> GetDependencySets_NoLock(Cancella using var seenProjects = SharedPools.Default>().GetPooledObject(); using var results = SharedPools.Default>>().GetPooledObject(); this.ComputeDependencySets(seenProjects.Object, results.Object, cancellationToken); - _lazyDependencySets = results.Object.ToImmutableArray(); + _lazyDependencySets = [.. results.Object]; } return _lazyDependencySets; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs index 56a51c3f5750a..d7e48d3bf3dd3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectDependencyGraph_AddProjectReference.cs @@ -66,7 +66,7 @@ private static ImmutableDictionary> Compu } else { - return existingReferencesMap.SetItem(projectId, referencedProjectIds.ToImmutableHashSet()); + return existingReferencesMap.SetItem(projectId, [.. referencedProjectIds]); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 899adeef5bfe6..960c7f165945d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -1176,7 +1176,7 @@ public SolutionCompilationState WithFrozenSourceGeneratedDocuments( var documentStatesByProjectId = documentStates.ToDictionary(static state => state.Id.ProjectId); var newTrackerMap = CreateCompilationTrackerMap( - documentStatesByProjectId.Keys.ToImmutableArray(), + [.. documentStatesByProjectId.Keys], this.SolutionState.GetProjectDependencyGraph(), static (trackerMap, arg) => { @@ -1570,7 +1570,7 @@ private SolutionCompilationState RemoveDocumentsFromMultipleProjects( var removedDocumentStatesForProject = removedDocumentStates.ToImmutable(); - var compilationTranslationAction = removeDocumentsFromProjectState(oldProjectState, documentIdsInProject.ToImmutableArray(), removedDocumentStatesForProject); + var compilationTranslationAction = removeDocumentsFromProjectState(oldProjectState, [.. documentIdsInProject], removedDocumentStatesForProject); var newProjectState = compilationTranslationAction.NewProjectState; var stateChange = newCompilationState.SolutionState.ForkProject( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 4d3f278d7554a..6bf1728591683 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -1178,7 +1178,7 @@ public static ProjectDependencyGraph CreateDependencyGraph( state.ProjectReferences.Where(pr => projectStates.ContainsKey(pr.ProjectId)).Select(pr => pr.ProjectId).ToImmutableHashSet())) .ToImmutableDictionary(); - return new ProjectDependencyGraph(projectIds.ToImmutableHashSet(), map); + return new ProjectDependencyGraph([.. projectIds], map); } public SolutionState WithOptions(SolutionOptionSet options) diff --git a/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs b/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs index 4c40c9a8b6565..f948925ecc2f0 100644 --- a/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs +++ b/src/Workspaces/CoreTest/BatchFixAllProviderTests.cs @@ -107,7 +107,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) if (_nested) { - fixes = [CodeAction.Create("Container", fixes.ToImmutableArray(), isInlinable: false)]; + fixes = [CodeAction.Create("Container", [.. fixes], isInlinable: false)]; } foreach (var fix in fixes) diff --git a/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs b/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs index c1a68021dae2b..4c80e4cd799c2 100644 --- a/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs +++ b/src/Workspaces/CoreTest/EditorConfigParsing/EditorConfigFileParserTests.cs @@ -23,7 +23,7 @@ internal static EditorConfigFile CreateParseResults(string e list.Add(parseResult); } - return new EditorConfigFile(editorconfigFilePath, list.ToImmutableArray()); + return new EditorConfigFile(editorconfigFilePath, [.. list]); } [Fact] diff --git a/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs b/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs index 8f28f33ba1510..b5d5d44d9e1dd 100644 --- a/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs +++ b/src/Workspaces/CoreTest/FindAllDeclarationsTests.TestSolutionsAndProject.cs @@ -62,7 +62,7 @@ private static void Verify(SolutionKind workspaceKind, IEnumerable decl private static void VerifyResults(IEnumerable declarations, string[] expectedResults) { declarations = declarations.OrderBy(d => d.ToString()); - expectedResults = expectedResults.OrderBy(r => r).ToArray(); + expectedResults = [.. expectedResults.OrderBy(r => r)]; for (var i = 0; i < expectedResults.Length; i++) { diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index db2774e7943e2..6f023828ec35f 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -3935,7 +3935,7 @@ public void TestUpdateDocumentsOrder() var pid = ProjectId.CreateNewId(); VersionStamp GetVersion() => solution.GetProject(pid).Version; - ImmutableArray GetDocumentIds() => solution.GetProject(pid).DocumentIds.ToImmutableArray(); + ImmutableArray GetDocumentIds() => [.. solution.GetProject(pid).DocumentIds]; ImmutableArray GetSyntaxTrees() { return solution.GetProject(pid).GetCompilationAsync().Result.SyntaxTrees.ToImmutableArray(); diff --git a/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs b/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs index 63807101f0a66..b9e1680f9a148 100644 --- a/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/TryApplyChangesTests.cs @@ -31,7 +31,7 @@ public CustomizedCanApplyWorkspace(ApplyChangesKind[] allowedKinds, Func? canApplyCompilationOptions = null) : base(Host.Mef.MefHostServices.DefaultHost, workspaceKind: nameof(CustomizedCanApplyWorkspace)) { - _allowedKinds = allowedKinds.ToImmutableArray(); + _allowedKinds = [.. allowedKinds]; _canApplyParseOptions = canApplyParseOptions; _canApplyCompilationOptions = canApplyCompilationOptions; diff --git a/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs b/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs index da2266620118d..95f12c9ac6ba3 100644 --- a/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs +++ b/src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs @@ -33,9 +33,9 @@ public sealed class TestComposition public CacheKey(ImmutableHashSet assemblies, ImmutableHashSet parts, ImmutableHashSet excludedPartTypes) { - _assemblies = assemblies.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName)).ToImmutableArray(); - _parts = parts.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName)).ToImmutableArray(); - _excludedPartTypes = excludedPartTypes.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName)).ToImmutableArray(); + _assemblies = [.. assemblies.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; + _parts = [.. parts.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; + _excludedPartTypes = [.. excludedPartTypes.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; } public override bool Equals(object? obj) diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs index ef18057196fcd..565746f8dc581 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace_XmlConsumption.cs @@ -902,7 +902,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)netcore30).HasValue && ((bool?)netcore30).Value) { - references = NetCoreApp.References.ToList(); + references = [.. NetCoreApp.References]; } var netstandard20 = element.Attribute(CommonReferencesNetStandard20Name); @@ -910,7 +910,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)netstandard20).HasValue && ((bool?)netstandard20).Value) { - references = TargetFrameworkUtil.NetStandard20References.ToList(); + references = [.. TargetFrameworkUtil.NetStandard20References]; } var net6 = element.Attribute(CommonReferencesNet6Name); @@ -918,7 +918,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)net6).HasValue && ((bool?)net6).Value) { - references = TargetFrameworkUtil.GetReferences(TargetFramework.Net60).ToList(); + references = [.. TargetFrameworkUtil.GetReferences(TargetFramework.Net60)]; } var net7 = element.Attribute(CommonReferencesNet7Name); @@ -926,7 +926,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)net7).HasValue && ((bool?)net7).Value) { - references = TargetFrameworkUtil.GetReferences(TargetFramework.Net70).ToList(); + references = [.. TargetFrameworkUtil.GetReferences(TargetFramework.Net70)]; } var net8 = element.Attribute(CommonReferencesNet8Name); @@ -934,7 +934,7 @@ private IList CreateCommonReferences(XElement element) ((bool?)net8).HasValue && ((bool?)net8).Value) { - references = TargetFrameworkUtil.GetReferences(TargetFramework.Net80).ToList(); + references = [.. TargetFrameworkUtil.GetReferences(TargetFramework.Net80)]; } var mincorlib = element.Attribute(CommonReferencesMinCorlibName); diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs index 309159decf2be..af62f5337cb67 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspace`1.cs @@ -638,7 +638,7 @@ internal void InitializeDocuments( Documents.Add(submission.Documents.Single()); } - var solution = CreateSolution(projectNameToTestHostProject.Values.ToArray()); + var solution = CreateSolution([.. projectNameToTestHostProject.Values]); AddTestSolution(solution); foreach (var projectElement in workspaceElement.Elements(ProjectElementName)) diff --git a/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs b/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs index 0b331f550e052..1b405b7c66a44 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AsynchronousOperationListener/RemoteAsynchronousOperationListenerService.cs @@ -41,7 +41,7 @@ public ValueTask IsCompletedAsync(ImmutableArray featureNames, Can var exportProvider = workspace.Services.SolutionServices.ExportProvider; var listenerProvider = exportProvider.GetExports().Single().Value; - return new ValueTask(!listenerProvider.HasPendingWaiter(featureNames.ToArray())); + return new ValueTask(!listenerProvider.HasPendingWaiter([.. featureNames])); }, cancellationToken); } @@ -53,7 +53,7 @@ public ValueTask ExpeditedWaitAsync(ImmutableArray featureNames, Cancell var exportProvider = workspace.Services.SolutionServices.ExportProvider; var listenerProvider = exportProvider.GetExports().Single().Value; - await listenerProvider.WaitAllAsync(workspace, featureNames.ToArray()).ConfigureAwait(false); + await listenerProvider.WaitAllAsync(workspace, [.. featureNames]).ConfigureAwait(false); }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs index 2db59acf92969..e593b1d8928de 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassification/RemoteSemanticClassificationService.cs @@ -52,7 +52,7 @@ await AbstractClassificationService.AddClassificationsInCurrentProcessAsync( _workQueue.AddWork((document, type, options)); } - return SerializableClassifiedSpans.Dehydrate(temp.ToImmutableArray()); + return SerializableClassifiedSpans.Dehydrate([.. temp]); }, cancellationToken); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs index e18cb1274e9d3..2e538e86dd901 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs @@ -58,8 +58,7 @@ public IList FormatRange( // Exception 2: Similar behavior for do-while if (common.ContainsDiagnostics && !CloseBraceOfTryOrDoBlock(endToken)) { - smartTokenformattingRules = ImmutableArray.Empty.Add( - new NoLineChangeFormattingRule()).AddRange(_formattingRules); + smartTokenformattingRules = [new NoLineChangeFormattingRule(), .. _formattingRules]; } var formatter = CSharpSyntaxFormatting.Instance; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs index e1509de06a403..da3c86ea8f721 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/INamedTypeSymbolExtensions.cs @@ -393,7 +393,7 @@ private static ImmutableArray GetInterfacesToImplement( cancellationToken.ThrowIfCancellationRequested(); interfacesToImplement.RemoveRange(alreadyImplementedInterfaces); - return interfacesToImplement.ToImmutableArray(); + return [.. interfacesToImplement]; } private static ImmutableArray GetUnimplementedMembers( @@ -560,7 +560,7 @@ public static ImmutableArray GetOverridableMembers( RemoveNonOverriddableMembers(result, containingType, cancellationToken); } - return result.Keys.OrderBy(s => result[s]).ToImmutableArray(); + return [.. result.Keys.OrderBy(s => result[s])]; static void RemoveOverriddenMembers( Dictionary result, INamedTypeSymbol containingType, CancellationToken cancellationToken) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs index c5cf608238719..fa6a76cf05a04 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.AnalysisData.cs @@ -86,7 +86,7 @@ protected AnalysisData() /// public SymbolUsageResult ToResult() => new(SymbolsWriteBuilder.ToImmutableDictionary(), - SymbolsReadBuilder.ToImmutableHashSet()); + [.. SymbolsReadBuilder]); public BasicBlockAnalysisData AnalyzeLocalFunctionInvocation(IMethodSymbol localFunction, CancellationToken cancellationToken) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs index 1a25bc7db5577..8b0b6e1b3b97a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/FlowAnalysis/SymbolUsageAnalysis/SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.cs @@ -196,7 +196,7 @@ private static ImmutableHashSet GetCapturedLocals(ControlFlowGraph } } - return builder.ToImmutableHashSet(); + return [.. builder]; } public BasicBlockAnalysisData GetBlockAnalysisData(BasicBlock basicBlock) @@ -600,7 +600,7 @@ public override bool TryGetDelegateInvocationTargets(IOperation write, out Immut // Attempts to return potential lamba/local function delegate invocation targets for the given write. if (_reachingDelegateCreationTargets.TryGetValue(write, out var targetsBuilder)) { - targets = targetsBuilder.ToImmutableHashSet(); + targets = [.. targetsBuilder]; return true; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs index 99aeca8efb44c..4bf5a87ee261d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/FileBannerFacts/AbstractFileBannerFacts.cs @@ -157,7 +157,7 @@ public TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives BuildFoldersFromNamespace(string? @namespac } var parts = @namespace.Split(NamespaceSeparatorArray, options: StringSplitOptions.RemoveEmptyEntries); - return parts.ToImmutableArray(); + return [.. parts]; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs index 9886c1975190c..83d4ad3474067 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/RestrictedInternalsVisibleToAttribute.cs @@ -10,5 +10,5 @@ namespace System.Runtime.CompilerServices; internal sealed class RestrictedInternalsVisibleToAttribute(string assemblyName, params string[] allowedNamespaces) : Attribute { public string AssemblyName { get; } = assemblyName; - public ImmutableArray AllowedNamespaces { get; } = allowedNamespaces.ToImmutableArray(); + public ImmutableArray AllowedNamespaces { get; } = [.. allowedNamespaces]; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs index aaeae082f5d20..884ebc4e3f51d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CodeGeneration/CSharpCodeGenerationService.cs @@ -501,7 +501,7 @@ public override TDeclarationNode AddStatements( } else if (destinationMember is AccessorDeclarationSyntax accessorDeclaration) { - return (accessorDeclaration.Body == null) ? destinationMember : Cast(accessorDeclaration.AddBodyStatements(StatementGenerator.GenerateStatements(statements).ToArray())); + return (accessorDeclaration.Body == null) ? destinationMember : Cast(accessorDeclaration.AddBodyStatements([.. StatementGenerator.GenerateStatements(statements)])); } else if (destinationMember is CompilationUnitSyntax compilationUnit && info.Context.BestLocation is null) { @@ -520,7 +520,7 @@ public override TDeclarationNode AddStatements( // statement container. If the global statement is not already a block, create a block which can hold // both the original statement and any new statements we are adding to it. var block = statement as BlockSyntax ?? Block(statement); - return Cast(block.AddStatements(StatementGenerator.GenerateStatements(statements).ToArray())); + return Cast(block.AddStatements([.. StatementGenerator.GenerateStatements(statements)])); } else { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs index 09b58b5e6a7fe..3839eb370ebbc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationHelpers.cs @@ -71,7 +71,7 @@ public static void GetNameAndInnermostNamespace( break; } - name = string.Join(".", names.ToArray()); + name = string.Join(".", [.. names]); } else { @@ -336,7 +336,7 @@ public static int GetInsertionIndex( // The list was grouped (by type, staticness, accessibility). Try to find a location // to put the new declaration into. - var result = Array.BinarySearch(declarationList.ToArray(), declaration, comparerWithoutNameCheck); + var result = Array.BinarySearch([.. declarationList], declaration, comparerWithoutNameCheck); var desiredGroupIndex = result < 0 ? ~result : result; Debug.Assert(desiredGroupIndex >= 0); Debug.Assert(desiredGroupIndex <= declarationList.Count); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs index 5d525a7b31c10..45c058621bda5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs @@ -109,7 +109,7 @@ public virtual ImmutableArray ReturnTypeCustomModifiers public ImmutableArray UnmanagedCallingConventionTypes => []; public IMethodSymbol Construct(params ITypeSymbol[] typeArguments) - => new CodeGenerationConstructedMethodSymbol(this, typeArguments.ToImmutableArray()); + => new CodeGenerationConstructedMethodSymbol(this, [.. typeArguments]); public IMethodSymbol Construct(ImmutableArray typeArguments, ImmutableArray typeArgumentNullableAnnotations) => new CodeGenerationConstructedMethodSymbol(this, typeArguments); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs index 5b06942521e2d..445328ff50423 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs @@ -63,7 +63,7 @@ public INamedTypeSymbol Construct(params ITypeSymbol[] typeArguments) } return new CodeGenerationConstructedNamedTypeSymbol( - ConstructedFrom, typeArguments.ToImmutableArray(), this.TypeMembers); + ConstructedFrom, [.. typeArguments], this.TypeMembers); } public INamedTypeSymbol Construct(ImmutableArray typeArguments, ImmutableArray typeArgumentNullableAnnotations) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs index fcf86c82b58ca..bc4197cc204c1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Helpers/MefHostServicesHelpers.cs @@ -27,7 +27,7 @@ public static ImmutableArray LoadNearbyAssemblies(IEnumerable } } - return assemblies.ToImmutableArray(); + return [.. assemblies]; } private static Assembly TryLoadNearbyAssembly(string assemblySimpleName) From 25dd646672af5657657a5a28fe1980655981b173 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 18:34:36 -0700 Subject: [PATCH 016/292] revert --- .../CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs | 8 ++++---- .../Core/Source/ResultProvider/Helpers/ArrayBuilder.cs | 2 +- .../Core/Test/FunctionResolver/Process.cs | 2 +- .../Core/Test/FunctionResolver/Resolver.cs | 2 +- .../Core/Test/ResultProvider/ReflectionUtilities.cs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs index 1c629b457c386..27a7d762f5b1c 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/UsingDebugInfoTests.cs @@ -382,9 +382,9 @@ public void BadPdb_ForwardChain() { switch (token) { - case methodToken1: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken2).Build().Bytes]; - case methodToken2: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken3).Build().Bytes]; - case methodToken3: return [.. new MethodDebugInfoBytes.Builder([new[] { importString }]).Build().Bytes]; + case methodToken1: return new MethodDebugInfoBytes.Builder().AddForward(methodToken2).Build().Bytes.ToArray(); + case methodToken2: return new MethodDebugInfoBytes.Builder().AddForward(methodToken3).Build().Bytes.ToArray(); + case methodToken3: return new MethodDebugInfoBytes.Builder([new[] { importString }]).Build().Bytes.ToArray(); default: throw null; } }); @@ -421,7 +421,7 @@ public void BadPdb_Cycle() { switch (token) { - case methodToken1: return [.. new MethodDebugInfoBytes.Builder().AddForward(methodToken1).Build().Bytes]; + case methodToken1: return new MethodDebugInfoBytes.Builder().AddForward(methodToken1).Build().Bytes.ToArray(); default: throw null; } }); diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs index b87194ad1065d..89e03e291c67e 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/Helpers/ArrayBuilder.cs @@ -91,7 +91,7 @@ public void Free() public T[] ToArray() { - return [.. _items]; + return _items.ToArray(); } public T[] ToArrayAndFree() diff --git a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs index 1da9a346b6325..adbf1368e2732 100644 --- a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs +++ b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Process.cs @@ -40,7 +40,7 @@ internal void AddModule(Module module) internal Module[] GetModules() { - return [.. _modules]; + return _modules.ToArray(); } void IDisposable.Dispose() diff --git a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs index 04651d7b9dc9a..6f9f3d7ad366f 100644 --- a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs +++ b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Resolver.cs @@ -73,7 +73,7 @@ internal override Request[] GetRequests(Process process) { return new Request[0]; } - return [.. requests]; + return requests.ToArray(); } internal override string GetRequestModuleName(Request request) diff --git a/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs b/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs index 4f43d28015f4f..c46088fa8a4c1 100644 --- a/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs +++ b/src/ExpressionEvaluator/Core/Test/ResultProvider/ReflectionUtilities.cs @@ -16,7 +16,7 @@ internal static class ReflectionUtilities { internal static Assembly Load(ImmutableArray assembly) { - return Assembly.Load([.. assembly]); + return Assembly.Load(assembly.ToArray()); } internal static object Instantiate(this Type type, params object[] args) From 0296c7c34a717f91e1652c603384adb5b90b4f17 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 18:37:11 -0700 Subject: [PATCH 017/292] revert --- .../Protocol/Features/DecompiledSource/AssemblyResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs b/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs index e68ef976c26f6..00d5807883c73 100644 --- a/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs +++ b/src/Features/LanguageServer/Protocol/Features/DecompiledSource/AssemblyResolver.cs @@ -64,7 +64,7 @@ public PEFile TryResolve(MetadataReference metadataReference, PEStreamOptions st { if (_inMemoryImagesForTesting.TryGetValue(metadataReference, out var pair)) { - return new PEFile(pair.fileName, new MemoryStream([.. pair.image]), streamOptions); + return new PEFile(pair.fileName, new MemoryStream(pair.image.ToArray()), streamOptions); } return null; From 727b0da6676bb7849d3ece5879deab7e4c0d78c9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 18:39:52 -0700 Subject: [PATCH 018/292] revert --- .../Host/Interactive/Core/InteractiveHostPlatformInfo.cs | 2 +- .../Host/Interactive/Core/RemoteExecutionResult.cs | 4 ++-- .../Host/Interactive/Core/RemoteInitializationResult.cs | 4 ++-- .../InteractiveSessionReferencesTests.cs | 4 ++-- src/Scripting/Core/Hosting/SynchronizedList.cs | 2 +- .../PdbUtilities/EditAndContinue/EditAndContinueTest.cs | 4 ++-- src/Test/PdbUtilities/Reader/SymReaderFactory.cs | 2 +- src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs | 2 +- src/Tools/BuildActionTelemetryTable/Program.cs | 5 +++-- src/Tools/BuildValidator/CompilationDiff.cs | 8 ++++---- src/Tools/BuildValidator/Program.cs | 4 ++-- .../Razor/RazorSpanMappingServiceWrapper.cs | 2 +- src/Tools/Replay/Replay.cs | 2 +- .../BuildTask/GenerateFilteredReferenceAssembliesTask.cs | 8 ++++---- src/Tools/Source/RunTests/AssemblyScheduler.cs | 4 ++-- src/Tools/Source/RunTests/Program.cs | 2 +- src/Tools/Source/RunTests/TestRunner.cs | 2 +- 17 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs index 0f901c2bb2478..8066dae54677e 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHostPlatformInfo.cs @@ -44,7 +44,7 @@ public Data Serialize() => new Data() { HasGlobalAssemblyCache = HasGlobalAssemblyCache, - PlatformAssemblyPaths = [.. PlatformAssemblyPaths], + PlatformAssemblyPaths = PlatformAssemblyPaths.ToArray(), }; public static InteractiveHostPlatformInfo GetCurrentPlatformInfo() diff --git a/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs b/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs index 7b54f0437ba74..1fd4f2c9b7e87 100644 --- a/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs +++ b/src/Interactive/Host/Interactive/Core/RemoteExecutionResult.cs @@ -64,8 +64,8 @@ public Data Serialize() => new Data() { Success = Success, - SourcePaths = [.. SourcePaths], - ReferencePaths = [.. ReferencePaths], + SourcePaths = SourcePaths.ToArray(), + ReferencePaths = ReferencePaths.ToArray(), WorkingDirectory = WorkingDirectory, InitializationResult = InitializationResult?.Serialize(), }; diff --git a/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs b/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs index e1cac0b878d68..7dc06aaabe5b2 100644 --- a/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs +++ b/src/Interactive/Host/Interactive/Core/RemoteInitializationResult.cs @@ -43,8 +43,8 @@ public Data Serialize() => new Data() { ScriptPath = ScriptPath, - MetadataReferencePaths = [.. MetadataReferencePaths], - Imports = [.. Imports], + MetadataReferencePaths = MetadataReferencePaths.ToArray(), + Imports = Imports.ToArray(), }; } } diff --git a/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs b/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs index ceaeb09bbb656..c236c73222d8f 100644 --- a/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs +++ b/src/Scripting/CSharpTest.Desktop/InteractiveSessionReferencesTests.cs @@ -532,7 +532,7 @@ public async Task MissingRefrencesAutoResolution() var portableLibRef = portableLib.ToMetadataReference(); var loader = new InteractiveAssemblyLoader(); - loader.RegisterDependency(Assembly.Load([.. portableLib.EmitToArray()])); + loader.RegisterDependency(Assembly.Load(portableLib.EmitToArray().ToArray())); var s0 = await CSharpScript.Create("new C()", options: ScriptOptions.Default.AddReferences(portableLibRef), assemblyLoader: loader).RunAsync(); var c0 = s0.Script.GetCompilation(); @@ -569,7 +569,7 @@ public void HostObjectInInMemoryAssembly() var libImage = lib.EmitToArray(); var libRef = MetadataImageReference.CreateFromImage(libImage); - var libAssembly = Assembly.Load([.. libImage]); + var libAssembly = Assembly.Load(libImage.ToArray()); var globalsType = libAssembly.GetType("C"); var globals = Activator.CreateInstance(globalsType); diff --git a/src/Scripting/Core/Hosting/SynchronizedList.cs b/src/Scripting/Core/Hosting/SynchronizedList.cs index a6e399afb7a69..1b99490d96b50 100644 --- a/src/Scripting/Core/Hosting/SynchronizedList.cs +++ b/src/Scripting/Core/Hosting/SynchronizedList.cs @@ -83,7 +83,7 @@ public IEnumerator GetEnumerator() lock (_guard) { // make a copy to ensure thread-safe enumeration - return ((IEnumerable)[.. _list]).GetEnumerator(); + return ((IEnumerable)_list.ToArray()).GetEnumerator(); } } diff --git a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs index 3cdb0a2d003c0..f20f773ac3597 100644 --- a/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs +++ b/src/Test/PdbUtilities/EditAndContinue/EditAndContinueTest.cs @@ -117,10 +117,10 @@ internal TSelf Verify() } readers.Add(generation.MetadataReader); - var verifier = new GenerationVerifier(index, generation, [.. readers]); + var verifier = new GenerationVerifier(index, generation, readers.ToImmutableArray()); generation.Verifier(verifier); - exceptions.Add([.. verifier.Exceptions]); + exceptions.Add(verifier.Exceptions.ToImmutableArray()); index++; } diff --git a/src/Test/PdbUtilities/Reader/SymReaderFactory.cs b/src/Test/PdbUtilities/Reader/SymReaderFactory.cs index d15171d69a9cd..8968f175f0b5a 100644 --- a/src/Test/PdbUtilities/Reader/SymReaderFactory.cs +++ b/src/Test/PdbUtilities/Reader/SymReaderFactory.cs @@ -72,7 +72,7 @@ public static ISymUnmanagedReader5 CreateReader(byte[] pdbImage, byte[] peImageO public static ISymUnmanagedReader5 CreateReader(ImmutableArray pdbImage, ImmutableArray peImageOpt = default(ImmutableArray)) { - return CreateReader(new MemoryStream([.. pdbImage]), (peImageOpt.IsDefault) ? null : new PEReader(peImageOpt)); + return CreateReader(new MemoryStream(pdbImage.ToArray()), (peImageOpt.IsDefault) ? null : new PEReader(peImageOpt)); } public static ISymUnmanagedReader5 CreateReader(Stream pdbStream, Stream peStreamOpt = null) diff --git a/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs b/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs index f5dffe99106d3..61de9bce50d17 100644 --- a/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs +++ b/src/Test/PdbUtilities/Reader/Token2SourceLineExporter.cs @@ -1467,7 +1467,7 @@ private static void LoadDbiStream(BitAccess bits, out DbiModuleInfo[] modules, o if (modList.Count > 0) { - modules = [.. modList]; + modules = modList.ToArray(); } else { diff --git a/src/Tools/BuildActionTelemetryTable/Program.cs b/src/Tools/BuildActionTelemetryTable/Program.cs index 0adb386609d4f..d03b7331e2d4e 100644 --- a/src/Tools/BuildActionTelemetryTable/Program.cs +++ b/src/Tools/BuildActionTelemetryTable/Program.cs @@ -493,10 +493,11 @@ static bool isCodeActionProviderType(Type t) => typeof(CodeFixProvider).IsAssign internal static ImmutableArray<(string TypeName, string Hash)> GetTelemetryInfos(ImmutableArray codeActionAndProviderTypes) { - return [.. codeActionAndProviderTypes + return codeActionAndProviderTypes .Distinct(FullNameTypeComparer.Instance) .Select(GetTelemetryInfo) - .OrderBy(info => info.TypeName)]; + .OrderBy(info => info.TypeName) + .ToImmutableArray(); static (string TypeName, string Hash) GetTelemetryInfo(Type type) { diff --git a/src/Tools/BuildValidator/CompilationDiff.cs b/src/Tools/BuildValidator/CompilationDiff.cs index ed741dbe60ccb..a8d24aca794c2 100644 --- a/src/Tools/BuildValidator/CompilationDiff.cs +++ b/src/Tools/BuildValidator/CompilationDiff.cs @@ -176,7 +176,7 @@ MetadataReader getRebuildPdbReader() { if (hasEmbeddedPdb) { - var peReader = new PEReader([.. rebuildBytes]); + var peReader = new PEReader(rebuildBytes.ToImmutableArray()); return peReader.GetEmbeddedPdbMetadataReader() ?? throw ExceptionUtilities.Unreachable(); } else @@ -261,8 +261,8 @@ void writeBinaryDiffArtifacts() Debug.Assert(_rebuildPdbReader is object); Debug.Assert(_rebuildCompilation is object); - var originalPeReader = new PEReader([.. _originalPortableExecutableBytes]); - var rebuildPeReader = new PEReader([.. _rebuildPortableExecutableBytes]); + var originalPeReader = new PEReader(_originalPortableExecutableBytes.ToImmutableArray()); + var rebuildPeReader = new PEReader(_rebuildPortableExecutableBytes.ToImmutableArray()); var originalInfo = new BuildInfo( AssemblyBytes: _originalPortableExecutableBytes, AssemblyReader: originalPeReader, @@ -401,7 +401,7 @@ void writeEmbeddedFileInfo() { if (!info.CompressedHash.IsDefaultOrEmpty) { - var hashString = BitConverter.ToString([.. info.CompressedHash]).Replace("-", ""); + var hashString = BitConverter.ToString(info.CompressedHash.ToArray()).Replace("-", ""); writer.WriteLine($@"\t""{Path.GetFileName(info.SourceTextInfo.OriginalSourceFilePath)}"" - {hashString}"); } } diff --git a/src/Tools/BuildValidator/Program.cs b/src/Tools/BuildValidator/Program.cs index 7b74aa2bb1ada..7f19bb818826a 100644 --- a/src/Tools/BuildValidator/Program.cs +++ b/src/Tools/BuildValidator/Program.cs @@ -108,7 +108,7 @@ static int HandleCommand(string[] assembliesPath, string[]? exclude, string sour excludes.Add(Path.DirectorySeparatorChar + "runtimes" + Path.DirectorySeparatorChar); excludes.Add(@".resources.dll"); - var options = new Options(assembliesPath, referencesPath, [.. excludes], sourcePath, verbose, quiet, debug, debugPath); + var options = new Options(assembliesPath, referencesPath, excludes.ToArray(), sourcePath, verbose, quiet, debug, debugPath); // TODO: remove the DemoLoggerProvider or convert it to something more permanent var loggerFactory = LoggerFactory.Create(builder => @@ -221,7 +221,7 @@ private static AssemblyInfo[] GetAssemblyInfos( } } - return [.. map.Values.OrderBy(x => x.FileName, FileNameEqualityComparer.StringComparer)]; + return map.Values.OrderBy(x => x.FileName, FileNameEqualityComparer.StringComparer).ToArray(); static IEnumerable getAssemblyPaths(string directory) { diff --git a/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs b/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs index 64b1a31fdfa0d..d186da23ac528 100644 --- a/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/RazorSpanMappingServiceWrapper.cs @@ -65,7 +65,7 @@ public override async Task> MapSpansAsync( } } - return [.. roslynSpans]; + return roslynSpans.ToImmutableArray(); } } } diff --git a/src/Tools/Replay/Replay.cs b/src/Tools/Replay/Replay.cs index 2467e612b5105..444b79364ae03 100644 --- a/src/Tools/Replay/Replay.cs +++ b/src/Tools/Replay/Replay.cs @@ -196,7 +196,7 @@ static async Task BuildAsync( var request = BuildServerConnection.CreateBuildRequest( outputName, compilerCall.IsCSharp ? RequestLanguage.CSharpCompile : RequestLanguage.VisualBasicCompile, - [.. args], + args.ToList(), workingDirectory: compilerCall.ProjectDirectory, tempDirectory: options.TempDirectory, keepAlive: null, diff --git a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs index 8c18e4f662dc1..1bfd842325f35 100644 --- a/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs +++ b/src/Tools/SemanticSearch/BuildTask/GenerateFilteredReferenceAssembliesTask.cs @@ -106,7 +106,7 @@ internal void ExecuteImpl(IEnumerable<(string apiSpecPath, IReadOnlyList } var peImageBuffer = File.ReadAllBytes(originalReferencePath); - Rewrite(peImageBuffer, [.. patterns]); + Rewrite(peImageBuffer, patterns.ToImmutableArray()); try { @@ -302,21 +302,21 @@ internal static unsafe void Rewrite(byte[] peImage, ImmutableArray p writer, metadataReader, patterns, - [.. types.OrderBy(t => t.MetadataToken)], + types.OrderBy(t => t.MetadataToken).ToImmutableArray(), metadataOffset); UpdateMethodDefinitions( writer, metadataReader, patterns, - [.. methods.OrderBy(t => t.MetadataToken)], + methods.OrderBy(t => t.MetadataToken).ToImmutableArray(), metadataOffset); UpdateFieldDefinitions( writer, metadataReader, patterns, - [.. fields.OrderBy(t => t.MetadataToken)], + fields.OrderBy(t => t.MetadataToken).ToImmutableArray(), metadataOffset); // unsign: diff --git a/src/Tools/Source/RunTests/AssemblyScheduler.cs b/src/Tools/Source/RunTests/AssemblyScheduler.cs index 56eb6bb308f1f..4f54d609f98a8 100644 --- a/src/Tools/Source/RunTests/AssemblyScheduler.cs +++ b/src/Tools/Source/RunTests/AssemblyScheduler.cs @@ -112,7 +112,7 @@ static ImmutableArray CreateWorkItemsForFullAssemblies(ImmutableAr workItems.Add(new WorkItemInfo(currentWorkItem, partitionIndex++)); } - return [.. workItems]; + return workItems.ToImmutableArray(); } } @@ -235,7 +235,7 @@ private ImmutableArray BuildWorkItems( // Add any remaining tests to the work item. AddCurrentWorkItem(); - return [.. workItems]; + return workItems.ToImmutableArray(); void AddCurrentWorkItem() { diff --git a/src/Tools/Source/RunTests/Program.cs b/src/Tools/Source/RunTests/Program.cs index 121cd0dc85dc9..104bdcad356bb 100644 --- a/src/Tools/Source/RunTests/Program.cs +++ b/src/Tools/Source/RunTests/Program.cs @@ -351,7 +351,7 @@ private static ImmutableArray GetAssemblyFilePaths(Options options } list.Sort(); - return [.. list]; + return list.ToImmutableArray(); static bool shouldInclude(string name, Options options) { diff --git a/src/Tools/Source/RunTests/TestRunner.cs b/src/Tools/Source/RunTests/TestRunner.cs index 2d21960723ac5..f837f34a2e8d9 100644 --- a/src/Tools/Source/RunTests/TestRunner.cs +++ b/src/Tools/Source/RunTests/TestRunner.cs @@ -406,7 +406,7 @@ internal async Task RunAllAsync(ImmutableArray workI processResults.AddRange(c.ProcessResults); } - return new RunAllResult((failures == 0), [.. completed], processResults.ToImmutable()); + return new RunAllResult((failures == 0), completed.ToImmutableArray(), processResults.ToImmutable()); } private void Print(List testResults) From a4c1cd6d6fd2d4ea8f8802bed168cd60267a0811 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 19:10:52 -0700 Subject: [PATCH 019/292] Lint --- .../CodeFixes/Configuration/ConfigurationUpdater.cs | 8 ++++---- .../Portable/UnusedReferences/UnusedReferencesRemover.cs | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs index 3c1d7a5391de9..68c79177b07fc 100644 --- a/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs +++ b/src/Features/Core/Portable/CodeFixes/Configuration/ConfigurationUpdater.cs @@ -392,10 +392,10 @@ internal static ImmutableArray GetCodeStyleOptionsForDiagnostic(Diagno { if (IDEDiagnosticIdToOptionMappingHelper.TryGetMappedOptions(diagnostic.Id, project.Language, out var options)) { - return [.. (from option in options - where option.DefaultValue is ICodeStyleOption - orderby option.Definition.ConfigName - select option)]; + return [.. from option in options + where option.DefaultValue is ICodeStyleOption + orderby option.Definition.ConfigName + select option]; } return []; diff --git a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs index f70b8941c613c..2d42b5bec4dfc 100644 --- a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs +++ b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs @@ -256,8 +256,7 @@ internal static ImmutableArray GetAllCompilationAssemblies(ReferenceInfo { var transitiveCompilationAssemblies = reference.Dependencies .SelectMany(dependency => GetAllCompilationAssemblies(dependency)); - return [.. reference.CompilationAssemblies -, .. transitiveCompilationAssemblies]; + return [.. reference.CompilationAssemblies, .. transitiveCompilationAssemblies]; } public static async Task UpdateReferencesAsync( From 9da4b2de55b1679fb1c04e67f8de13a7d315d2ba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 12 Apr 2024 19:19:54 -0700 Subject: [PATCH 020/292] lint --- .../ConvertForEachToLinqQuery/AbstractToMethodConverter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs index 54be9ac17f079..61f9cf84c86fe 100644 --- a/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs +++ b/src/Features/CSharp/Portable/ConvertLinq/ConvertForEachToLinqQuery/AbstractToMethodConverter.cs @@ -124,8 +124,7 @@ void Convert(ExpressionSyntax replacingExpression, SyntaxNode nodeToRemoveIfFoll // Output: // return queryGenerated.ToList(); or return queryGenerated.Count(); replacingExpression = returnStatement.Expression; - leadingTrivia = [.. GetTriviaFromNode(nodeToRemoveIfFollowedByReturn) -, .. SyntaxNodeOrTokenExtensions.GetTrivia(replacingExpression)]; + leadingTrivia = [.. GetTriviaFromNode(nodeToRemoveIfFollowedByReturn), .. SyntaxNodeOrTokenExtensions.GetTrivia(replacingExpression)]; editor.RemoveNode(nodeToRemoveIfFollowedByReturn); } else From 3292c2a43eabd1476a6f98e9981611e55aaba3c8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 10:47:30 -0700 Subject: [PATCH 021/292] Simplify code action threading code --- .../Suggestions/SuggestedActionsSource.cs | 90 +++++-------------- 1 file changed, 20 insertions(+), 70 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index be975381ed17e..a6ec9d59d538c 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -146,68 +146,6 @@ public Task HasSuggestedActionsAsync( throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); } - private async Task GetSpanAsync(ReferenceCountedDisposable state, SnapshotSpan range, CancellationToken cancellationToken) - { - // First, ensure that the snapshot we're being asked about is for an actual - // roslyn document. This can fail, for example, in projection scenarios where - // we are called with a range snapshot that refers to the projection buffer - // and not the actual roslyn code that is being projected into it. - var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); - if (document == null) - { - return null; - } - - // Also make sure the range is from the same buffer that this source was created for - Contract.ThrowIfFalse( - range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), - $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); - - // Next, before we do any async work, acquire the user's selection, directly grabbing - // it from the UI thread if that's what we're on. That way we don't have any reentrancy - // blocking concerns if VS wants to block on this call (for example, if the user - // explicitly invokes the 'show smart tag' command). - // - // This work must happen on the UI thread as it needs to access the _textView's mutable - // state. - // - // Note: we may be called in one of two VS scenarios: - // 1) User has moved caret to a new line. In this case VS will call into us in the - // bg to see if we have any suggested actions for this line. In order to figure - // this out, we need to see what selection the user has (for refactorings), which - // necessitates going back to the fg. - // - // 2) User moves to a line and immediately hits ctrl-dot. In this case, on the UI - // thread VS will kick us off and then immediately block to get the results so - // that they can expand the light-bulb. In this case we cannot do BG work first, - // then call back into the UI thread to try to get the user selection. This will - // deadlock as the UI thread is blocked on us. - // - // There are two solution to '2'. Either introduce reentrancy (which we really don't - // like to do), or just ensure that we acquire and get the users selection up front. - // This means that when we're called from the UI thread, we never try to go back to the - // UI thread. - TextSpan? selection = null; - if (_threadingContext.JoinableTaskContext.IsOnMainThread) - { - selection = TryGetCodeRefactoringSelection(state, range); - } - else - { - await _threadingContext.InvokeBelowInputPriorityAsync(() => - { - // Make sure we were not disposed between kicking off this work and getting to this point. - using var state = _state.TryAddReference(); - if (state is null) - return; - - selection = TryGetCodeRefactoringSelection(state, range); - }, cancellationToken).ConfigureAwait(false); - } - - return selection; - } - private static async Task GetFixLevelAsync( ReferenceCountedDisposable state, TextDocument document, @@ -215,6 +153,8 @@ await _threadingContext.InvokeBelowInputPriorityAsync(() => CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) { + // Ensure we yield the thread that called into us, allowing it to continue onwards. + await Task.Yield().ConfigureAwait(false); var lowPriorityAnalyzers = new ConcurrentSet(); foreach (var order in Orderings) @@ -258,6 +198,9 @@ await _threadingContext.InvokeBelowInputPriorityAsync(() => CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) { + // Ensure we yield the thread that called into us, allowing it to continue onwards. + await Task.Yield().ConfigureAwait(false); + using var state = _state.TryAddReference(); if (state is null) return null; @@ -318,6 +261,11 @@ private void OnTextViewClosed(object sender, EventArgs e) if (state is null) return null; + // Make sure the range is from the same buffer that this source was created for. + Contract.ThrowIfFalse( + range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), + $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); + var workspace = state.Target.Workspace; if (workspace == null) return null; @@ -338,16 +286,18 @@ private void OnTextViewClosed(object sender, EventArgs e) using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var linkedToken = linkedTokenSource.Token; - var errorTask = Task.Run(() => GetFixLevelAsync(state, document, range, fallbackOptions, linkedToken), linkedToken); + // Kick off the work to get errors. + var errorTask = GetFixLevelAsync(state, document, range, fallbackOptions, linkedToken); - var selection = await GetSpanAsync(state, range, linkedToken).ConfigureAwait(false); + // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + var selection = TryGetCodeRefactoringSelection(state, range); + await Task.Yield().ConfigureAwait(false); - var refactoringTask = SpecializedTasks.Null(); - if (selection != null) - { - refactoringTask = Task.Run( - () => TryGetRefactoringSuggestedActionCategoryAsync(document, selection, fallbackOptions, linkedToken), linkedToken); - } + // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. + var refactoringTask = selection != null + ? TryGetRefactoringSuggestedActionCategoryAsync(document, selection, fallbackOptions, linkedToken) + : SpecializedTasks.Null(); // If we happen to get the result of the error task before the refactoring task, // and that result is non-null, we can just cancel the refactoring task. From a5aaa796a08e2a1db5f74f387805d6f9c08d0e0c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 10:48:14 -0700 Subject: [PATCH 022/292] remove unnecessary call --- .../Core/Def/Workspace/VisualStudioSymbolNavigationService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs index c703b64497112..8013cdac4f854 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs @@ -180,7 +180,6 @@ await navigationService.TryNavigateToSpanAsync( public async Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project project, CancellationToken cancellationToken) { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - _threadingContext.ThrowIfNotOnUIThread(); var definitionItem = symbol.ToNonClassifiedDefinitionItem(project.Solution, includeHiddenLocations: true); definitionItem.Properties.TryGetValue(DefinitionItem.RQNameKey1, out var rqName); From ea01eefb1bd37016074ff8a1b14b92bdd2955d08 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 10:53:13 -0700 Subject: [PATCH 023/292] Move to normal switch call. --- .../Suggestions/SuggestedActionsSource.cs | 2 +- .../Utilities/IThreadingContextExtensions.cs | 57 ------------------- .../KeybindingResetDetector.cs | 9 ++- src/VisualStudio/Core/Def/RoslynPackage.cs | 2 +- .../Impl/CodeModel/ProjectCodeModelFactory.cs | 17 +++++- 5 files changed, 22 insertions(+), 65 deletions(-) delete mode 100644 src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index a6ec9d59d538c..014764f072a0e 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -290,7 +290,7 @@ private void OnTextViewClosed(object sender, EventArgs e) var errorTask = GetFixLevelAsync(state, document, range, fallbackOptions, linkedToken); // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); var selection = TryGetCodeRefactoringSelection(state, range); await Task.Yield().ConfigureAwait(false); diff --git a/src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs b/src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs deleted file mode 100644 index 069816e08b4e7..0000000000000 --- a/src/EditorFeatures/Core/Shared/Utilities/IThreadingContextExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities; - -internal static class IThreadingContextExtensions -{ - /// - /// Returns true if any keyboard or mouse button input is pending on the message queue. - /// - public static bool IsInputPending() - { - // The code below invokes into user32.dll, which is not available in non-Windows. - if (PlatformInformation.IsUnix) - { - return false; - } - - // The return value of GetQueueStatus is HIWORD:LOWORD. - // A non-zero value in HIWORD indicates some input message in the queue. - var result = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT); - - const uint InputMask = NativeMethods.QS_INPUT | (NativeMethods.QS_INPUT << 16); - return (result & InputMask) != 0; - } - - public static Task InvokeBelowInputPriorityAsync(this IThreadingContext threadingContext, Action action, CancellationToken cancellationToken = default) - { - if (threadingContext.JoinableTaskContext.IsOnMainThread && !IsInputPending()) - { - // Optimize to inline the action if we're already on the foreground thread - // and there's no pending user input. - action(); - - return Task.CompletedTask; - } - else - { - return Task.Factory.SafeStartNewFromAsync( - async () => - { - await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - action(); - }, - cancellationToken, - TaskScheduler.Default); - } - } -} diff --git a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs index f0eab888879e2..0711327e2ebf1 100644 --- a/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs +++ b/src/VisualStudio/Core/Def/KeybindingReset/KeybindingResetDetector.cs @@ -101,15 +101,14 @@ public KeybindingResetDetector( _infoBar = new VisualStudioInfoBar(threadingContext, vsInfoBarUIFactory, vsShell, listenerProvider, windowFrame: null); } - public Task InitializeAsync() + public async Task InitializeAsync(CancellationToken cancellationToken) { // Immediately bail if the user has asked to never see this bar again. if (_globalOptions.GetOption(KeybindingResetOptionsStorage.NeverShowAgain)) - { - return Task.CompletedTask; - } + return; - return _threadingContext.InvokeBelowInputPriorityAsync(InitializeCore); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + InitializeCore(); } private void InitializeCore() diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index 79a33a370c61e..93730649e413e 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -273,7 +273,7 @@ private async Task LoadComponentsBackgroundAsync(CancellationToken cancellationT await LoadStackTraceExplorerMenusAsync(cancellationToken).ConfigureAwait(true); // Initialize keybinding reset detector - await ComponentModel.DefaultExportProvider.GetExportedValue().InitializeAsync().ConfigureAwait(true); + await ComponentModel.DefaultExportProvider.GetExportedValue().InitializeAsync(cancellationToken).ConfigureAwait(true); } private async Task LoadStackTraceExplorerMenusAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index 75d7d1abb1dd7..3c1acd9e7c9aa 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -110,7 +110,7 @@ private async ValueTask ProcessNextDocumentBatchAsync( // Keep firing events for this doc, as long as we haven't exceeded the max amount // of waiting time, and there's no user input that should take precedence. - if (stopwatch.Elapsed.Ticks > MaxTimeSlice || IThreadingContextExtensions.IsInputPending()) + if (stopwatch.Elapsed.Ticks > MaxTimeSlice || IsInputPending()) { await this.Listener.Delay(delayBetweenProcessing, cancellationToken).ConfigureAwait(true); stopwatch = SharedStopwatch.StartNew(); @@ -140,6 +140,21 @@ void FireEventsForDocument(DocumentId documentId) codeModel.FireEvents(); return; } + + // Returns true if any keyboard or mouse button input is pending on the message queue. + static bool IsInputPending() + { + // The code below invokes into user32.dll, which is not available in non-Windows. + if (PlatformInformation.IsUnix) + return false; + + // The return value of GetQueueStatus is HIWORD:LOWORD. + // A non-zero value in HIWORD indicates some input message in the queue. + var result = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT); + + const uint InputMask = NativeMethods.QS_INPUT | (NativeMethods.QS_INPUT << 16); + return (result & InputMask) != 0; + } } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) From e4d4c1d0088fdfaf88297f7250966b4a5978109a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 11:54:54 -0700 Subject: [PATCH 024/292] Pass in state --- .../Core.Wpf/Suggestions/SuggestedActionsSource.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 014764f072a0e..58aee5d6f22c9 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -193,6 +193,7 @@ public Task HasSuggestedActionsAsync( } private async Task TryGetRefactoringSuggestedActionCategoryAsync( + ReferenceCountedDisposable state, TextDocument document, TextSpan? selection, CodeActionOptionsProvider fallbackOptions, @@ -201,10 +202,6 @@ public Task HasSuggestedActionsAsync( // Ensure we yield the thread that called into us, allowing it to continue onwards. await Task.Yield().ConfigureAwait(false); - using var state = _state.TryAddReference(); - if (state is null) - return null; - if (!selection.HasValue) { // this is here to fail test and see why it is failed. @@ -296,7 +293,7 @@ private void OnTextViewClosed(object sender, EventArgs e) // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. var refactoringTask = selection != null - ? TryGetRefactoringSuggestedActionCategoryAsync(document, selection, fallbackOptions, linkedToken) + ? TryGetRefactoringSuggestedActionCategoryAsync(state, document, selection, fallbackOptions, linkedToken) : SpecializedTasks.Null(); // If we happen to get the result of the error task before the refactoring task, From 388ac6028c9eb9d2cc0dc53f2427555e4299d072 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 12:02:40 -0700 Subject: [PATCH 025/292] Refactor --- .../Suggestions/SuggestedActionsSource.cs | 373 ++++++++---------- .../SuggestedActionsSource_Async.cs | 2 +- 2 files changed, 175 insertions(+), 200 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 58aee5d6f22c9..aa6c0676f7b95 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -24,134 +24,168 @@ using Roslyn.Utilities; using IUIThreadOperationContext = Microsoft.VisualStudio.Utilities.IUIThreadOperationContext; -namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions +namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; + +internal partial class SuggestedActionsSourceProvider { - internal partial class SuggestedActionsSourceProvider + private sealed partial class SuggestedActionsSource : ISuggestedActionsSource3 { - private sealed partial class SuggestedActionsSource : ISuggestedActionsSource3 - { - private readonly ISuggestedActionCategoryRegistryService _suggestedActionCategoryRegistry; + private readonly ISuggestedActionCategoryRegistryService _suggestedActionCategoryRegistry; - private readonly ReferenceCountedDisposable _state; - private readonly IAsynchronousOperationListener _listener; + private readonly ReferenceCountedDisposable _state; + private readonly IAsynchronousOperationListener _listener; - public event EventHandler? SuggestedActionsChanged { add { } remove { } } + public event EventHandler? SuggestedActionsChanged { add { } remove { } } - private readonly IThreadingContext _threadingContext; - public readonly IGlobalOptionService GlobalOptions; + private readonly IThreadingContext _threadingContext; + public readonly IGlobalOptionService GlobalOptions; - public SuggestedActionsSource( - IThreadingContext threadingContext, - IGlobalOptionService globalOptions, - SuggestedActionsSourceProvider owner, - ITextView textView, - ITextBuffer textBuffer, - ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, - IAsynchronousOperationListener listener) - { - _threadingContext = threadingContext; - GlobalOptions = globalOptions; + public SuggestedActionsSource( + IThreadingContext threadingContext, + IGlobalOptionService globalOptions, + SuggestedActionsSourceProvider owner, + ITextView textView, + ITextBuffer textBuffer, + ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, + IAsynchronousOperationListener listener) + { + _threadingContext = threadingContext; + GlobalOptions = globalOptions; + + _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; + _state = new ReferenceCountedDisposable(new State(this, owner, textView, textBuffer)); + + _state.Target.TextView.Closed += OnTextViewClosed; + _listener = listener; + } + + public void Dispose() + => _state.Dispose(); - _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; - _state = new ReferenceCountedDisposable(new State(this, owner, textView, textBuffer)); + public bool TryGetTelemetryId(out Guid telemetryId) + { + telemetryId = default; - _state.Target.TextView.Closed += OnTextViewClosed; - _listener = listener; + using var state = _state.TryAddReference(); + if (state is null) + { + return false; } - public void Dispose() + var workspace = state.Target.Workspace; + if (workspace == null) { - _state.Dispose(); + return false; } - private ReferenceCountedDisposable SourceState => _state; + var documentId = workspace.GetDocumentIdInCurrentContext(state.Target.SubjectBuffer.AsTextContainer()); + if (documentId == null) + { + return false; + } - public bool TryGetTelemetryId(out Guid telemetryId) + var project = workspace.CurrentSolution.GetProject(documentId.ProjectId); + if (project == null) { - telemetryId = default; + return false; + } - using var state = _state.TryAddReference(); - if (state is null) - { + switch (project.Language) + { + case LanguageNames.CSharp: + telemetryId = s_CSharpSourceGuid; + return true; + case LanguageNames.VisualBasic: + telemetryId = s_visualBasicSourceGuid; + return true; + case "Xaml": + telemetryId = s_xamlSourceGuid; + return true; + default: return false; - } + } + } - var workspace = state.Target.Workspace; - if (workspace == null) - { - return false; - } + public IEnumerable? GetSuggestedActions( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + CancellationToken cancellationToken) + => null; + + public IEnumerable? GetSuggestedActions( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + IUIThreadOperationContext operationContext) + => null; + + public Task HasSuggestedActionsAsync( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + CancellationToken cancellationToken) + { + // We implement GetSuggestedActionCategoriesAsync so this should not be called + throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); + } - var documentId = workspace.GetDocumentIdInCurrentContext(state.Target.SubjectBuffer.AsTextContainer()); - if (documentId == null) - { - return false; - } + private void OnTextViewClosed(object sender, EventArgs e) + => Dispose(); - var project = workspace.CurrentSolution.GetProject(documentId.ProjectId); - if (project == null) - { - return false; - } + public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) + { + using var state = _state.TryAddReference(); + if (state is null) + return null; - switch (project.Language) - { - case LanguageNames.CSharp: - telemetryId = s_CSharpSourceGuid; - return true; - case LanguageNames.VisualBasic: - telemetryId = s_visualBasicSourceGuid; - return true; - case "Xaml": - telemetryId = s_xamlSourceGuid; - return true; - default: - return false; - } - } + // Make sure the range is from the same buffer that this source was created for. + Contract.ThrowIfFalse( + range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), + $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); - public IEnumerable? GetSuggestedActions( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - CancellationToken cancellationToken) - => null; + var workspace = state.Target.Workspace; + if (workspace == null) + return null; - public IEnumerable? GetSuggestedActions( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - IUIThreadOperationContext operationContext) - => null; + // never show light bulb if solution is not fully loaded yet + if (!await workspace.Services.GetRequiredService().IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false)) + return null; - private static string GetFixCategory(DiagnosticSeverity severity) - { - switch (severity) - { - case DiagnosticSeverity.Hidden: - case DiagnosticSeverity.Info: - case DiagnosticSeverity.Warning: - return PredefinedSuggestedActionCategoryNames.CodeFix; - case DiagnosticSeverity.Error: - return PredefinedSuggestedActionCategoryNames.ErrorFix; - default: - throw ExceptionUtilities.Unreachable(); - } - } + cancellationToken.ThrowIfCancellationRequested(); - public Task HasSuggestedActionsAsync( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - CancellationToken cancellationToken) - { - // We implement GetSuggestedActionCategoriesAsync so this should not be called - throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); - } + using var asyncToken = state.Target.Owner.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); + var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); + if (document == null) + return null; + + var fallbackOptions = GlobalOptions.GetCodeActionOptionsProvider(); + + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + // Assign over cancellation token so no one accidentally uses the wrong token. + cancellationToken = linkedTokenSource.Token; + + // Kick off the work to get errors. + var errorTask = GetFixLevelAsync(); - private static async Task GetFixLevelAsync( - ReferenceCountedDisposable state, - TextDocument document, - SnapshotSpan range, - CodeActionOptionsProvider fallbackOptions, - CancellationToken cancellationToken) + // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + + var selection = TryGetCodeRefactoringSelection(state, range); + await Task.Yield().ConfigureAwait(false); + + // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. + var refactoringTask = selection != null + ? TryGetRefactoringSuggestedActionCategoryAsync(selection) + : SpecializedTasks.Null(); + + // If we happen to get the result of the error task before the refactoring task, + // and that result is non-null, we can just cancel the refactoring task. + var result = await errorTask.ConfigureAwait(false) ?? await refactoringTask.ConfigureAwait(false); + linkedTokenSource.Cancel(); + + return result == null + ? null + : _suggestedActionCategoryRegistry.CreateSuggestedActionCategorySet(result); + + async Task GetFixLevelAsync() { // Ensure we yield the thread that called into us, allowing it to continue onwards. await Task.Yield().ConfigureAwait(false); @@ -163,41 +197,42 @@ public Task HasSuggestedActionsAsync( Contract.ThrowIfNull(priority); var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers); - var result = await GetFixLevelAsync(priorityProvider).ConfigureAwait(false); + var result = await GetFixCategoryAsync(priorityProvider).ConfigureAwait(false); if (result != null) return result; } return null; + } - async Task GetFixLevelAsync(ICodeActionRequestPriorityProvider priorityProvider) + async Task GetFixCategoryAsync(ICodeActionRequestPriorityProvider priorityProvider) + { + if (state.Target.Owner._codeFixService != null && + state.Target.SubjectBuffer.SupportsCodeFixes()) { - if (state.Target.Owner._codeFixService != null && - state.Target.SubjectBuffer.SupportsCodeFixes()) - { - var result = await state.Target.Owner._codeFixService.GetMostSevereFixAsync( - document, range.Span.ToTextSpan(), priorityProvider, fallbackOptions, cancellationToken).ConfigureAwait(false); + var result = await state.Target.Owner._codeFixService.GetMostSevereFixAsync( + document, range.Span.ToTextSpan(), priorityProvider, fallbackOptions, cancellationToken).ConfigureAwait(false); - if (result.HasFix) + if (result.HasFix) + { + Logger.Log(FunctionId.SuggestedActions_HasSuggestedActionsAsync); + return result.CodeFixCollection.FirstDiagnostic.Severity switch { - Logger.Log(FunctionId.SuggestedActions_HasSuggestedActionsAsync); - return GetFixCategory(result.CodeFixCollection.FirstDiagnostic.Severity); - } - if (!result.UpToDate) - return null; + DiagnosticSeverity.Hidden or DiagnosticSeverity.Info or DiagnosticSeverity.Warning => PredefinedSuggestedActionCategoryNames.CodeFix, + DiagnosticSeverity.Error => PredefinedSuggestedActionCategoryNames.ErrorFix, + _ => throw ExceptionUtilities.Unreachable(), + }; } - return null; + if (!result.UpToDate) + return null; } + + return null; } - private async Task TryGetRefactoringSuggestedActionCategoryAsync( - ReferenceCountedDisposable state, - TextDocument document, - TextSpan? selection, - CodeActionOptionsProvider fallbackOptions, - CancellationToken cancellationToken) + async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) { // Ensure we yield the thread that called into us, allowing it to continue onwards. await Task.Yield().ConfigureAwait(false); @@ -222,89 +257,29 @@ public Task HasSuggestedActionsAsync( return null; } + } - private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) - { - _threadingContext.ThrowIfNotOnUIThread(); - - var selectedSpans = state.Target.TextView.Selection.SelectedSpans - .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) - .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) - .ToList(); - - // We only support refactorings when there is a single selection in the document. - if (selectedSpans.Count != 1) - { - return null; - } - - var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); - - // We only support refactorings when selected span intersects with the span that the light bulb is asking for. - if (!translatedSpan.IntersectsWith(range)) - { - return null; - } - - return translatedSpan.Span.ToTextSpan(); - } - - private void OnTextViewClosed(object sender, EventArgs e) - => Dispose(); - - public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) - { - using var state = _state.TryAddReference(); - if (state is null) - return null; - - // Make sure the range is from the same buffer that this source was created for. - Contract.ThrowIfFalse( - range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), - $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); - - var workspace = state.Target.Workspace; - if (workspace == null) - return null; - - // never show light bulb if solution is not fully loaded yet - if (!await workspace.Services.GetRequiredService().IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false)) - return null; - - cancellationToken.ThrowIfCancellationRequested(); - - using var asyncToken = state.Target.Owner.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); - var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); - if (document == null) - return null; - - var fallbackOptions = GlobalOptions.GetCodeActionOptionsProvider(); - - using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var linkedToken = linkedTokenSource.Token; + private TextSpan? TryGetCodeRefactoringSelection( + ReferenceCountedDisposable state, SnapshotSpan range) + { + _threadingContext.ThrowIfNotOnUIThread(); - // Kick off the work to get errors. - var errorTask = GetFixLevelAsync(state, document, range, fallbackOptions, linkedToken); + var selectedSpans = state.Target.TextView.Selection.SelectedSpans + .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) + .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) + .ToList(); - // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - var selection = TryGetCodeRefactoringSelection(state, range); - await Task.Yield().ConfigureAwait(false); + // We only support refactorings when there is a single selection in the document. + if (selectedSpans.Count != 1) + return null; - // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. - var refactoringTask = selection != null - ? TryGetRefactoringSuggestedActionCategoryAsync(state, document, selection, fallbackOptions, linkedToken) - : SpecializedTasks.Null(); + var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); - // If we happen to get the result of the error task before the refactoring task, - // and that result is non-null, we can just cancel the refactoring task. - var result = await errorTask.ConfigureAwait(false) ?? await refactoringTask.ConfigureAwait(false); - linkedTokenSource.Cancel(); + // We only support refactorings when selected span intersects with the span that the light bulb is asking for. + if (!translatedSpan.IntersectsWith(range)) + return null; - return result == null - ? null - : _suggestedActionCategoryRegistry.CreateSuggestedActionCategorySet(result); - } + return translatedSpan.Span.ToTextSpan(); } } } diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs index 1a78c33ae59e2..8745781c048a2 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs @@ -70,7 +70,7 @@ private async Task GetSuggestedActionsWorkerAsync( CancellationToken cancellationToken) { _threadingContext.ThrowIfNotOnUIThread(); - using var state = SourceState.TryAddReference(); + using var state = _state.TryAddReference(); if (state is null) return; From 76c0b95e11f820d5f113e9b3944f13b017f044e2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 12:06:38 -0700 Subject: [PATCH 026/292] Update src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs --- .../Core.Wpf/Suggestions/SuggestedActionsSource.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index aa6c0676f7b95..097ff5a8bf6b1 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -259,8 +259,7 @@ private void OnTextViewClosed(object sender, EventArgs e) } } - private TextSpan? TryGetCodeRefactoringSelection( - ReferenceCountedDisposable state, SnapshotSpan range) + private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) { _threadingContext.ThrowIfNotOnUIThread(); From 7632acbcebb4598cc8f5e79e2f58e9f547332b00 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 12:16:15 -0700 Subject: [PATCH 027/292] Move back --- .../Suggestions/SuggestedActionsSource.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 097ff5a8bf6b1..2df5a43055bc4 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -130,6 +130,28 @@ public Task HasSuggestedActionsAsync( private void OnTextViewClosed(object sender, EventArgs e) => Dispose(); + private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) + { + _threadingContext.ThrowIfNotOnUIThread(); + + var selectedSpans = state.Target.TextView.Selection.SelectedSpans + .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) + .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) + .ToList(); + + // We only support refactorings when there is a single selection in the document. + if (selectedSpans.Count != 1) + return null; + + var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); + + // We only support refactorings when selected span intersects with the span that the light bulb is asking for. + if (!translatedSpan.IntersectsWith(range)) + return null; + + return translatedSpan.Span.ToTextSpan(); + } + public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { using var state = _state.TryAddReference(); @@ -258,27 +280,5 @@ private void OnTextViewClosed(object sender, EventArgs e) return null; } } - - private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) - { - _threadingContext.ThrowIfNotOnUIThread(); - - var selectedSpans = state.Target.TextView.Selection.SelectedSpans - .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) - .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) - .ToList(); - - // We only support refactorings when there is a single selection in the document. - if (selectedSpans.Count != 1) - return null; - - var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); - - // We only support refactorings when selected span intersects with the span that the light bulb is asking for. - if (!translatedSpan.IntersectsWith(range)) - return null; - - return translatedSpan.Span.ToTextSpan(); - } } } From cc6944304f41d4976cdd4d4f56019d1df1ec1d5c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 12:17:19 -0700 Subject: [PATCH 028/292] move back --- .../Suggestions/SuggestedActionsSource.cs | 389 +++++++++--------- 1 file changed, 195 insertions(+), 194 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 2df5a43055bc4..22b0cb8618024 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -24,260 +24,261 @@ using Roslyn.Utilities; using IUIThreadOperationContext = Microsoft.VisualStudio.Utilities.IUIThreadOperationContext; -namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; - -internal partial class SuggestedActionsSourceProvider +namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions { - private sealed partial class SuggestedActionsSource : ISuggestedActionsSource3 + internal partial class SuggestedActionsSourceProvider { - private readonly ISuggestedActionCategoryRegistryService _suggestedActionCategoryRegistry; - - private readonly ReferenceCountedDisposable _state; - private readonly IAsynchronousOperationListener _listener; - - public event EventHandler? SuggestedActionsChanged { add { } remove { } } - - private readonly IThreadingContext _threadingContext; - public readonly IGlobalOptionService GlobalOptions; - - public SuggestedActionsSource( - IThreadingContext threadingContext, - IGlobalOptionService globalOptions, - SuggestedActionsSourceProvider owner, - ITextView textView, - ITextBuffer textBuffer, - ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, - IAsynchronousOperationListener listener) + private sealed partial class SuggestedActionsSource : ISuggestedActionsSource3 { - _threadingContext = threadingContext; - GlobalOptions = globalOptions; + private readonly ISuggestedActionCategoryRegistryService _suggestedActionCategoryRegistry; - _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; - _state = new ReferenceCountedDisposable(new State(this, owner, textView, textBuffer)); + private readonly ReferenceCountedDisposable _state; + private readonly IAsynchronousOperationListener _listener; - _state.Target.TextView.Closed += OnTextViewClosed; - _listener = listener; - } - - public void Dispose() - => _state.Dispose(); + public event EventHandler? SuggestedActionsChanged { add { } remove { } } - public bool TryGetTelemetryId(out Guid telemetryId) - { - telemetryId = default; + private readonly IThreadingContext _threadingContext; + public readonly IGlobalOptionService GlobalOptions; - using var state = _state.TryAddReference(); - if (state is null) + public SuggestedActionsSource( + IThreadingContext threadingContext, + IGlobalOptionService globalOptions, + SuggestedActionsSourceProvider owner, + ITextView textView, + ITextBuffer textBuffer, + ISuggestedActionCategoryRegistryService suggestedActionCategoryRegistry, + IAsynchronousOperationListener listener) { - return false; - } + _threadingContext = threadingContext; + GlobalOptions = globalOptions; - var workspace = state.Target.Workspace; - if (workspace == null) - { - return false; - } + _suggestedActionCategoryRegistry = suggestedActionCategoryRegistry; + _state = new ReferenceCountedDisposable(new State(this, owner, textView, textBuffer)); - var documentId = workspace.GetDocumentIdInCurrentContext(state.Target.SubjectBuffer.AsTextContainer()); - if (documentId == null) - { - return false; + _state.Target.TextView.Closed += OnTextViewClosed; + _listener = listener; } - var project = workspace.CurrentSolution.GetProject(documentId.ProjectId); - if (project == null) - { - return false; - } + public void Dispose() + => _state.Dispose(); - switch (project.Language) + public bool TryGetTelemetryId(out Guid telemetryId) { - case LanguageNames.CSharp: - telemetryId = s_CSharpSourceGuid; - return true; - case LanguageNames.VisualBasic: - telemetryId = s_visualBasicSourceGuid; - return true; - case "Xaml": - telemetryId = s_xamlSourceGuid; - return true; - default: + telemetryId = default; + + using var state = _state.TryAddReference(); + if (state is null) + { return false; - } - } + } - public IEnumerable? GetSuggestedActions( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - CancellationToken cancellationToken) - => null; - - public IEnumerable? GetSuggestedActions( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - IUIThreadOperationContext operationContext) - => null; - - public Task HasSuggestedActionsAsync( - ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, - CancellationToken cancellationToken) - { - // We implement GetSuggestedActionCategoriesAsync so this should not be called - throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); - } + var workspace = state.Target.Workspace; + if (workspace == null) + { + return false; + } - private void OnTextViewClosed(object sender, EventArgs e) - => Dispose(); + var documentId = workspace.GetDocumentIdInCurrentContext(state.Target.SubjectBuffer.AsTextContainer()); + if (documentId == null) + { + return false; + } - private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) - { - _threadingContext.ThrowIfNotOnUIThread(); + var project = workspace.CurrentSolution.GetProject(documentId.ProjectId); + if (project == null) + { + return false; + } - var selectedSpans = state.Target.TextView.Selection.SelectedSpans - .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) - .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) - .ToList(); + switch (project.Language) + { + case LanguageNames.CSharp: + telemetryId = s_CSharpSourceGuid; + return true; + case LanguageNames.VisualBasic: + telemetryId = s_visualBasicSourceGuid; + return true; + case "Xaml": + telemetryId = s_xamlSourceGuid; + return true; + default: + return false; + } + } - // We only support refactorings when there is a single selection in the document. - if (selectedSpans.Count != 1) - return null; + public IEnumerable? GetSuggestedActions( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + CancellationToken cancellationToken) + => null; + + public IEnumerable? GetSuggestedActions( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + IUIThreadOperationContext operationContext) + => null; + + public Task HasSuggestedActionsAsync( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + CancellationToken cancellationToken) + { + // We implement GetSuggestedActionCategoriesAsync so this should not be called + throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); + } - var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); + private void OnTextViewClosed(object sender, EventArgs e) + => Dispose(); - // We only support refactorings when selected span intersects with the span that the light bulb is asking for. - if (!translatedSpan.IntersectsWith(range)) - return null; + private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) + { + _threadingContext.ThrowIfNotOnUIThread(); - return translatedSpan.Span.ToTextSpan(); - } + var selectedSpans = state.Target.TextView.Selection.SelectedSpans + .SelectMany(ss => state.Target.TextView.BufferGraph.MapDownToBuffer(ss, SpanTrackingMode.EdgeExclusive, state.Target.SubjectBuffer)) + .Where(ss => !state.Target.TextView.IsReadOnlyOnSurfaceBuffer(ss)) + .ToList(); - public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) - { - using var state = _state.TryAddReference(); - if (state is null) - return null; + // We only support refactorings when there is a single selection in the document. + if (selectedSpans.Count != 1) + return null; - // Make sure the range is from the same buffer that this source was created for. - Contract.ThrowIfFalse( - range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), - $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); + var translatedSpan = selectedSpans[0].TranslateTo(range.Snapshot, SpanTrackingMode.EdgeInclusive); - var workspace = state.Target.Workspace; - if (workspace == null) - return null; + // We only support refactorings when selected span intersects with the span that the light bulb is asking for. + if (!translatedSpan.IntersectsWith(range)) + return null; - // never show light bulb if solution is not fully loaded yet - if (!await workspace.Services.GetRequiredService().IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false)) - return null; + return translatedSpan.Span.ToTextSpan(); + } - cancellationToken.ThrowIfCancellationRequested(); + public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) + { + using var state = _state.TryAddReference(); + if (state is null) + return null; - using var asyncToken = state.Target.Owner.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); - var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); - if (document == null) - return null; + // Make sure the range is from the same buffer that this source was created for. + Contract.ThrowIfFalse( + range.Snapshot.TextBuffer.Equals(state.Target.SubjectBuffer), + $"Invalid text buffer passed to {nameof(HasSuggestedActionsAsync)}"); - var fallbackOptions = GlobalOptions.GetCodeActionOptionsProvider(); + var workspace = state.Target.Workspace; + if (workspace == null) + return null; - using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - // Assign over cancellation token so no one accidentally uses the wrong token. - cancellationToken = linkedTokenSource.Token; + // never show light bulb if solution is not fully loaded yet + if (!await workspace.Services.GetRequiredService().IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false)) + return null; - // Kick off the work to get errors. - var errorTask = GetFixLevelAsync(); + cancellationToken.ThrowIfCancellationRequested(); - // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + using var asyncToken = state.Target.Owner.OperationListener.BeginAsyncOperation(nameof(GetSuggestedActionCategoriesAsync)); + var document = range.Snapshot.GetOpenTextDocumentInCurrentContextWithChanges(); + if (document == null) + return null; - var selection = TryGetCodeRefactoringSelection(state, range); - await Task.Yield().ConfigureAwait(false); + var fallbackOptions = GlobalOptions.GetCodeActionOptionsProvider(); - // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. - var refactoringTask = selection != null - ? TryGetRefactoringSuggestedActionCategoryAsync(selection) - : SpecializedTasks.Null(); + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + // Assign over cancellation token so no one accidentally uses the wrong token. + cancellationToken = linkedTokenSource.Token; - // If we happen to get the result of the error task before the refactoring task, - // and that result is non-null, we can just cancel the refactoring task. - var result = await errorTask.ConfigureAwait(false) ?? await refactoringTask.ConfigureAwait(false); - linkedTokenSource.Cancel(); + // Kick off the work to get errors. + var errorTask = GetFixLevelAsync(); - return result == null - ? null - : _suggestedActionCategoryRegistry.CreateSuggestedActionCategorySet(result); + // Make a quick jump back to the UI thread to get the user's selection, then go back to the thread pool.. + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - async Task GetFixLevelAsync() - { - // Ensure we yield the thread that called into us, allowing it to continue onwards. + var selection = TryGetCodeRefactoringSelection(state, range); await Task.Yield().ConfigureAwait(false); - var lowPriorityAnalyzers = new ConcurrentSet(); - foreach (var order in Orderings) - { - var priority = TryGetPriority(order); - Contract.ThrowIfNull(priority); - var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers); + // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. + var refactoringTask = selection != null + ? TryGetRefactoringSuggestedActionCategoryAsync(selection) + : SpecializedTasks.Null(); - var result = await GetFixCategoryAsync(priorityProvider).ConfigureAwait(false); - if (result != null) - return result; - } + // If we happen to get the result of the error task before the refactoring task, + // and that result is non-null, we can just cancel the refactoring task. + var result = await errorTask.ConfigureAwait(false) ?? await refactoringTask.ConfigureAwait(false); + linkedTokenSource.Cancel(); - return null; - } + return result == null + ? null + : _suggestedActionCategoryRegistry.CreateSuggestedActionCategorySet(result); - async Task GetFixCategoryAsync(ICodeActionRequestPriorityProvider priorityProvider) - { - if (state.Target.Owner._codeFixService != null && - state.Target.SubjectBuffer.SupportsCodeFixes()) + async Task GetFixLevelAsync() { - var result = await state.Target.Owner._codeFixService.GetMostSevereFixAsync( - document, range.Span.ToTextSpan(), priorityProvider, fallbackOptions, cancellationToken).ConfigureAwait(false); + // Ensure we yield the thread that called into us, allowing it to continue onwards. + await Task.Yield().ConfigureAwait(false); + var lowPriorityAnalyzers = new ConcurrentSet(); - if (result.HasFix) + foreach (var order in Orderings) { - Logger.Log(FunctionId.SuggestedActions_HasSuggestedActionsAsync); - return result.CodeFixCollection.FirstDiagnostic.Severity switch - { + var priority = TryGetPriority(order); + Contract.ThrowIfNull(priority); + var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers); - DiagnosticSeverity.Hidden or DiagnosticSeverity.Info or DiagnosticSeverity.Warning => PredefinedSuggestedActionCategoryNames.CodeFix, - DiagnosticSeverity.Error => PredefinedSuggestedActionCategoryNames.ErrorFix, - _ => throw ExceptionUtilities.Unreachable(), - }; + var result = await GetFixCategoryAsync(priorityProvider).ConfigureAwait(false); + if (result != null) + return result; } - if (!result.UpToDate) - return null; + return null; } - return null; - } + async Task GetFixCategoryAsync(ICodeActionRequestPriorityProvider priorityProvider) + { + if (state.Target.Owner._codeFixService != null && + state.Target.SubjectBuffer.SupportsCodeFixes()) + { + var result = await state.Target.Owner._codeFixService.GetMostSevereFixAsync( + document, range.Span.ToTextSpan(), priorityProvider, fallbackOptions, cancellationToken).ConfigureAwait(false); - async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) - { - // Ensure we yield the thread that called into us, allowing it to continue onwards. - await Task.Yield().ConfigureAwait(false); + if (result.HasFix) + { + Logger.Log(FunctionId.SuggestedActions_HasSuggestedActionsAsync); + return result.CodeFixCollection.FirstDiagnostic.Severity switch + { + + DiagnosticSeverity.Hidden or DiagnosticSeverity.Info or DiagnosticSeverity.Warning => PredefinedSuggestedActionCategoryNames.CodeFix, + DiagnosticSeverity.Error => PredefinedSuggestedActionCategoryNames.ErrorFix, + _ => throw ExceptionUtilities.Unreachable(), + }; + } + + if (!result.UpToDate) + return null; + } - if (!selection.HasValue) - { - // this is here to fail test and see why it is failed. - Trace.WriteLine("given range is not current"); return null; } - if (GlobalOptions.GetOption(EditorComponentOnOffOptions.CodeRefactorings) && - state.Target.Owner._codeRefactoringService != null && - state.Target.SubjectBuffer.SupportsRefactorings()) + async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) { - if (await state.Target.Owner._codeRefactoringService.HasRefactoringsAsync( - document, selection.Value, fallbackOptions, cancellationToken).ConfigureAwait(false)) + // Ensure we yield the thread that called into us, allowing it to continue onwards. + await Task.Yield().ConfigureAwait(false); + + if (!selection.HasValue) { - return PredefinedSuggestedActionCategoryNames.Refactoring; + // this is here to fail test and see why it is failed. + Trace.WriteLine("given range is not current"); + return null; } - } - return null; + if (GlobalOptions.GetOption(EditorComponentOnOffOptions.CodeRefactorings) && + state.Target.Owner._codeRefactoringService != null && + state.Target.SubjectBuffer.SupportsRefactorings()) + { + if (await state.Target.Owner._codeRefactoringService.HasRefactoringsAsync( + document, selection.Value, fallbackOptions, cancellationToken).ConfigureAwait(false)) + { + return PredefinedSuggestedActionCategoryNames.Refactoring; + } + } + + return null; + } } } } From 449f7120660a6a27a9fa05018d73248a933e4ff2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 12:26:59 -0700 Subject: [PATCH 029/292] move back --- .../Core.Wpf/Suggestions/SuggestedActionsSource.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index 22b0cb8618024..be74c5242f47f 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -127,9 +127,6 @@ public Task HasSuggestedActionsAsync( throw new NotImplementedException($"We implement {nameof(GetSuggestedActionCategoriesAsync)}. This should not be called."); } - private void OnTextViewClosed(object sender, EventArgs e) - => Dispose(); - private TextSpan? TryGetCodeRefactoringSelection(ReferenceCountedDisposable state, SnapshotSpan range) { _threadingContext.ThrowIfNotOnUIThread(); @@ -152,6 +149,9 @@ private void OnTextViewClosed(object sender, EventArgs e) return translatedSpan.Span.ToTextSpan(); } + private void OnTextViewClosed(object sender, EventArgs e) + => Dispose(); + public async Task GetSuggestedActionCategoriesAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { using var state = _state.TryAddReference(); From 556930ff20fe8e0c623341fa9e1197167c8fba2c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:11:51 -0700 Subject: [PATCH 030/292] Move to an iterative approach to determining next sibling in the blender --- .../CSharp/Portable/Parser/Blender.Cursor.cs | 45 +++++++++++++------ .../CSharp/Portable/Parser/Blender.Reader.cs | 4 +- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 4f9b6796441b9..d2dbd415c562f 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -55,28 +55,45 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) return token.Kind() == SyntaxKind.EndOfFileToken || token.FullWidth != 0; } - public Cursor MoveToNextSibling() + public static Cursor MoveToNextSibling(Cursor cursor) { - if (this.CurrentNodeOrToken.Parent != null) + var currentCursor = cursor; + while (currentCursor.CurrentNodeOrToken.UnderlyingNode != null) { - // First, look to the nodes to the right of this one in our parent's child list - // to get the next sibling. - var siblings = this.CurrentNodeOrToken.Parent.ChildNodesAndTokens(); - for (int i = _indexInParent + 1, n = siblings.Count; i < n; i++) + var nextSibling = moveToNextSiblingWorker(currentCursor); + + // If we got a valid sibling, return it. + if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) + return nextSibling; + + currentCursor = currentCursor.MoveToParent(); + } + + // Couldn't find anything, bail out. + return default; + + // Returns default if we should walk to the parent to try to find the next sibling. + // Returns the cursor of the next sibling if we find it. + static Cursor moveToNextSiblingWorker(Cursor cursor) + { + if (cursor.CurrentNodeOrToken.Parent != null) { - var sibling = siblings[i]; - if (IsNonZeroWidthOrIsEndOfFile(sibling)) + // First, look to the nodes to the right of this one in our parent's child list to get the next sibling. + var siblings = cursor.CurrentNodeOrToken.Parent.ChildNodesAndTokens(); + for (int i = cursor._indexInParent + 1, n = siblings.Count; i < n; i++) { - return new Cursor(sibling, i); + var sibling = siblings[i]; + if (IsNonZeroWidthOrIsEndOfFile(sibling)) + return new Cursor(sibling, i); } + + // We're at the end of this sibling chain. Have our caller walk up to the parent and see who is + // the next sibling of that. } - // We're at the end of this sibling chain. Walk up to the parent and see who is - // the next sibling of that. - return MoveToParent().MoveToNextSibling(); + // Don't have a parent, bail out. This will cause our caller itself to bail out. + return default; } - - return default(Cursor); } private Cursor MoveToParent() diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index f0e6877589899..4181b444e1c75 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -118,7 +118,7 @@ private void SkipOldToken() // Now, skip past it. _changeDelta += node.FullWidth; _oldDirectives = node.ApplyDirectives(_oldDirectives); - _oldTreeCursor = _oldTreeCursor.MoveToNextSibling(); + _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); // If our cursor is now after any changes, then just skip past them while upping // the changeDelta length. This will let us know that we need to read tokens @@ -204,7 +204,7 @@ private bool TryTakeOldNodeOrToken( // We can reuse this node or token. Move us forward in the new text, and move to the // next sibling. _newPosition += currentNodeOrToken.FullWidth; - _oldTreeCursor = _oldTreeCursor.MoveToNextSibling(); + _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); _newDirectives = currentNodeOrToken.ApplyDirectives(_newDirectives); _oldDirectives = currentNodeOrToken.ApplyDirectives(_oldDirectives); From 5a9fae0a67dfd113a9d0e957277de5c07b8a2bb0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:18:02 -0700 Subject: [PATCH 031/292] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index d2dbd415c562f..273db7c1982d3 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -57,16 +57,15 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) public static Cursor MoveToNextSibling(Cursor cursor) { - var currentCursor = cursor; - while (currentCursor.CurrentNodeOrToken.UnderlyingNode != null) + while (cursor.CurrentNodeOrToken.UnderlyingNode != null) { - var nextSibling = moveToNextSiblingWorker(currentCursor); + var nextSibling = moveToNextSiblingWorker(cursor); // If we got a valid sibling, return it. if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) return nextSibling; - currentCursor = currentCursor.MoveToParent(); + cursor = cursor.MoveToParent(); } // Couldn't find anything, bail out. From 210233fd6b5c361380033c4927a881ff299200f6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:21:13 -0700 Subject: [PATCH 032/292] Revert --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 273db7c1982d3..7f037104f0e69 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -77,7 +77,8 @@ static Cursor moveToNextSiblingWorker(Cursor cursor) { if (cursor.CurrentNodeOrToken.Parent != null) { - // First, look to the nodes to the right of this one in our parent's child list to get the next sibling. + // First, look to the nodes to the right of this one in our parent's child list + // to get the next sibling. var siblings = cursor.CurrentNodeOrToken.Parent.ChildNodesAndTokens(); for (int i = cursor._indexInParent + 1, n = siblings.Count; i < n; i++) { @@ -91,7 +92,7 @@ static Cursor moveToNextSiblingWorker(Cursor cursor) } // Don't have a parent, bail out. This will cause our caller itself to bail out. - return default; + return default(Cursor); } } From 8c0777413120ebc5b719391aa4eea16636b37b5a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:23:17 -0700 Subject: [PATCH 033/292] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 7f037104f0e69..492ca7f66354a 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -59,7 +59,7 @@ public static Cursor MoveToNextSibling(Cursor cursor) { while (cursor.CurrentNodeOrToken.UnderlyingNode != null) { - var nextSibling = moveToNextSiblingWorker(cursor); + var nextSibling = tryFindSiblingAtSameLevel(cursor); // If we got a valid sibling, return it. if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) @@ -71,9 +71,8 @@ public static Cursor MoveToNextSibling(Cursor cursor) // Couldn't find anything, bail out. return default; - // Returns default if we should walk to the parent to try to find the next sibling. - // Returns the cursor of the next sibling if we find it. - static Cursor moveToNextSiblingWorker(Cursor cursor) + // Returns the cursor of the next non-empty (or EOF) sibling in our parent if we find it, or `default` if we don't. + static Cursor tryFindSiblingAtSameLevel(Cursor cursor) { if (cursor.CurrentNodeOrToken.Parent != null) { From f79ba98339aefecdc65b072520a92a15b9d654f0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:27:10 -0700 Subject: [PATCH 034/292] Simplify --- .../CSharp/Portable/Parser/Blender.Cursor.cs | 50 +++++++------------ .../CSharp/Portable/Parser/Blender.Reader.cs | 17 +++++++ 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 492ca7f66354a..567a9b2fe477c 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -55,47 +55,35 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) return token.Kind() == SyntaxKind.EndOfFileToken || token.FullWidth != 0; } - public static Cursor MoveToNextSibling(Cursor cursor) + /// + /// Returns the cursor of the next non-empty (or EOF) sibling in our parent if we find it, or `default` if we don't. + /// + public Cursor TryFindNextNonEmptyOrEOFSibling() { - while (cursor.CurrentNodeOrToken.UnderlyingNode != null) + if (this.CurrentNodeOrToken.Parent != null) { - var nextSibling = tryFindSiblingAtSameLevel(cursor); - - // If we got a valid sibling, return it. - if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) - return nextSibling; - - cursor = cursor.MoveToParent(); - } - - // Couldn't find anything, bail out. - return default; - - // Returns the cursor of the next non-empty (or EOF) sibling in our parent if we find it, or `default` if we don't. - static Cursor tryFindSiblingAtSameLevel(Cursor cursor) - { - if (cursor.CurrentNodeOrToken.Parent != null) + // First, look to the nodes to the right of this one in our parent's child list + // to get the next sibling. + var siblings = this.CurrentNodeOrToken.Parent.ChildNodesAndTokens(); + for (int i = _indexInParent + 1, n = siblings.Count; i < n; i++) { - // First, look to the nodes to the right of this one in our parent's child list - // to get the next sibling. - var siblings = cursor.CurrentNodeOrToken.Parent.ChildNodesAndTokens(); - for (int i = cursor._indexInParent + 1, n = siblings.Count; i < n; i++) + var sibling = siblings[i]; + if (IsNonZeroWidthOrIsEndOfFile(sibling)) { - var sibling = siblings[i]; - if (IsNonZeroWidthOrIsEndOfFile(sibling)) - return new Cursor(sibling, i); + return new Cursor(sibling, i); } - - // We're at the end of this sibling chain. Have our caller walk up to the parent and see who is - // the next sibling of that. } - // Don't have a parent, bail out. This will cause our caller itself to bail out. - return default(Cursor); + // We're at the end of this sibling chain. Have our caller walk up to the parent and see who is + // the next sibling of that. } + + // Don't have a parent, bail out. This will cause our caller itself to bail when it tries to determine + // our parent. + return default(Cursor); } - private Cursor MoveToParent() + public Cursor MoveToParent() { var parent = this.CurrentNodeOrToken.Parent; var index = IndexOfNodeInParent(parent); diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index 4181b444e1c75..b75ff20d7f2ab 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -215,6 +215,23 @@ private bool TryTakeOldNodeOrToken( return true; } + private static Cursor MoveToNextSibling(Cursor cursor) + { + while (cursor.CurrentNodeOrToken.UnderlyingNode != null) + { + var nextSibling = cursor.TryFindNextNonEmptyOrEOFSibling(cursor); + + // If we got a valid sibling, return it. + if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) + return nextSibling; + + cursor = cursor.MoveToParent(); + } + + // Couldn't find anything, bail out. + return default; + } + private bool CanReuse(SyntaxNodeOrToken nodeOrToken) { // Zero width nodes and tokens always indicate that the parser had to do From 3e9e35ba0d27200a5a7bb9c124d79c1cbacb5695 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:28:15 -0700 Subject: [PATCH 035/292] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 2 +- src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 567a9b2fe477c..f581f66a61c90 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -58,7 +58,7 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) /// /// Returns the cursor of the next non-empty (or EOF) sibling in our parent if we find it, or `default` if we don't. /// - public Cursor TryFindNextNonEmptyOrEOFSibling() + public Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() { if (this.CurrentNodeOrToken.Parent != null) { diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index b75ff20d7f2ab..62135193d6ced 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -118,7 +118,7 @@ private void SkipOldToken() // Now, skip past it. _changeDelta += node.FullWidth; _oldDirectives = node.ApplyDirectives(_oldDirectives); - _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); + _oldTreeCursor = MoveToNextSibling(_oldTreeCursor); // If our cursor is now after any changes, then just skip past them while upping // the changeDelta length. This will let us know that we need to read tokens @@ -204,7 +204,7 @@ private bool TryTakeOldNodeOrToken( // We can reuse this node or token. Move us forward in the new text, and move to the // next sibling. _newPosition += currentNodeOrToken.FullWidth; - _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); + _oldTreeCursor = MoveToNextSibling(_oldTreeCursor); _newDirectives = currentNodeOrToken.ApplyDirectives(_newDirectives); _oldDirectives = currentNodeOrToken.ApplyDirectives(_oldDirectives); @@ -219,7 +219,7 @@ private static Cursor MoveToNextSibling(Cursor cursor) { while (cursor.CurrentNodeOrToken.UnderlyingNode != null) { - var nextSibling = cursor.TryFindNextNonEmptyOrEOFSibling(cursor); + var nextSibling = cursor.TryFindNextNonZeroWidthOrIsEndOfFileSibling(); // If we got a valid sibling, return it. if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) From 5725b5c6caf5104f56fa53f5c809ed9c3af10347 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:29:02 -0700 Subject: [PATCH 036/292] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index f581f66a61c90..37cd8d79fcc37 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -56,7 +56,8 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) } /// - /// Returns the cursor of the next non-empty (or EOF) sibling in our parent if we find it, or `default` if we don't. + /// Returns the cursor of our next non-empty (or EOF) sibling in our parent if one exists, or `default` if + /// if doesn't. /// public Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() { From 4112ba52e656a87d962c91a60851c34f16da0771 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:30:13 -0700 Subject: [PATCH 037/292] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs | 5 ----- src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index 37cd8d79fcc37..ad63898489e11 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -74,13 +74,8 @@ public Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() return new Cursor(sibling, i); } } - - // We're at the end of this sibling chain. Have our caller walk up to the parent and see who is - // the next sibling of that. } - // Don't have a parent, bail out. This will cause our caller itself to bail when it tries to determine - // our parent. return default(Cursor); } diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index 62135193d6ced..019eb02e47f1e 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -225,6 +225,7 @@ private static Cursor MoveToNextSibling(Cursor cursor) if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) return nextSibling; + // We're at the end of this sibling chain. Walk up to the parent and see who is the next sibling of that. cursor = cursor.MoveToParent(); } From 4d5a6e60f0253b2a1e989522c18f75950f90aaca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:30:42 -0700 Subject: [PATCH 038/292] Simplify --- src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index 019eb02e47f1e..f5c4283ba74ed 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -225,7 +225,8 @@ private static Cursor MoveToNextSibling(Cursor cursor) if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) return nextSibling; - // We're at the end of this sibling chain. Walk up to the parent and see who is the next sibling of that. + // We're at the end of this sibling chain. Walk up to the parent and see who is + // the next sibling of that. cursor = cursor.MoveToParent(); } From e6b07f307aabbf29cd59c427bccf095a8561ffd6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 14:31:26 -0700 Subject: [PATCH 039/292] Docs --- src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index f5c4283ba74ed..279195fb6d1cd 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -217,6 +217,7 @@ private bool TryTakeOldNodeOrToken( private static Cursor MoveToNextSibling(Cursor cursor) { + // Iteratively walk over the tree so that we don't stack overflow trying to recurse into anything. while (cursor.CurrentNodeOrToken.UnderlyingNode != null) { var nextSibling = cursor.TryFindNextNonZeroWidthOrIsEndOfFileSibling(); From 7ca862f582fdc82ef0676125a5bd7aea0b41dfd9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:12:29 -0700 Subject: [PATCH 040/292] Simplify --- .../CSharp/Portable/Parser/Blender.Cursor.cs | 24 +++++++++++++++++-- .../CSharp/Portable/Parser/Blender.Reader.cs | 24 ++----------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs index ad63898489e11..cab4183fbee13 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Cursor.cs @@ -59,7 +59,7 @@ private static bool IsNonZeroWidthOrIsEndOfFile(SyntaxNodeOrToken token) /// Returns the cursor of our next non-empty (or EOF) sibling in our parent if one exists, or `default` if /// if doesn't. /// - public Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() + private Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() { if (this.CurrentNodeOrToken.Parent != null) { @@ -79,13 +79,33 @@ public Cursor TryFindNextNonZeroWidthOrIsEndOfFileSibling() return default(Cursor); } - public Cursor MoveToParent() + private Cursor MoveToParent() { var parent = this.CurrentNodeOrToken.Parent; var index = IndexOfNodeInParent(parent); return new Cursor(parent, index); } + public static Cursor MoveToNextSibling(Cursor cursor) + { + // Iteratively walk over the tree so that we don't stack overflow trying to recurse into anything. + while (cursor.CurrentNodeOrToken.UnderlyingNode != null) + { + var nextSibling = cursor.TryFindNextNonZeroWidthOrIsEndOfFileSibling(); + + // If we got a valid sibling, return it. + if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) + return nextSibling; + + // We're at the end of this sibling chain. Walk up to the parent and see who is + // the next sibling of that. + cursor = cursor.MoveToParent(); + } + + // Couldn't find anything, bail out. + return default; + } + private static int IndexOfNodeInParent(SyntaxNode node) { if (node.Parent == null) diff --git a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs index 279195fb6d1cd..4181b444e1c75 100644 --- a/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs +++ b/src/Compilers/CSharp/Portable/Parser/Blender.Reader.cs @@ -118,7 +118,7 @@ private void SkipOldToken() // Now, skip past it. _changeDelta += node.FullWidth; _oldDirectives = node.ApplyDirectives(_oldDirectives); - _oldTreeCursor = MoveToNextSibling(_oldTreeCursor); + _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); // If our cursor is now after any changes, then just skip past them while upping // the changeDelta length. This will let us know that we need to read tokens @@ -204,7 +204,7 @@ private bool TryTakeOldNodeOrToken( // We can reuse this node or token. Move us forward in the new text, and move to the // next sibling. _newPosition += currentNodeOrToken.FullWidth; - _oldTreeCursor = MoveToNextSibling(_oldTreeCursor); + _oldTreeCursor = Cursor.MoveToNextSibling(_oldTreeCursor); _newDirectives = currentNodeOrToken.ApplyDirectives(_newDirectives); _oldDirectives = currentNodeOrToken.ApplyDirectives(_oldDirectives); @@ -215,26 +215,6 @@ private bool TryTakeOldNodeOrToken( return true; } - private static Cursor MoveToNextSibling(Cursor cursor) - { - // Iteratively walk over the tree so that we don't stack overflow trying to recurse into anything. - while (cursor.CurrentNodeOrToken.UnderlyingNode != null) - { - var nextSibling = cursor.TryFindNextNonZeroWidthOrIsEndOfFileSibling(); - - // If we got a valid sibling, return it. - if (nextSibling.CurrentNodeOrToken.UnderlyingNode != null) - return nextSibling; - - // We're at the end of this sibling chain. Walk up to the parent and see who is - // the next sibling of that. - cursor = cursor.MoveToParent(); - } - - // Couldn't find anything, bail out. - return default; - } - private bool CanReuse(SyntaxNodeOrToken nodeOrToken) { // Zero width nodes and tokens always indicate that the parser had to do From e341fdd1effca1e88a65f4e56c05de2c7a637518 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:30:45 -0700 Subject: [PATCH 041/292] Switch to an explicit stack to avoid stack overflow when computing tree equivalence --- .../Portable/Syntax/SyntaxEquivalence.cs | 192 ++++++++++-------- 1 file changed, 105 insertions(+), 87 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs index 6484b75a769c9..a41829fe01a4d 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.CSharp.Syntax { @@ -100,130 +101,147 @@ private static bool AreTokensEquivalent(GreenNode? before, GreenNode? after, Fun private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, Func? ignoreChildNode, bool topLevel) { - if (before == after) - { - return true; - } + var stack = ArrayBuilder<(GreenNode? before, GreenNode? after)>.GetInstance(); + stack.Push((before, after)); - if (before == null || after == null) + try { - return false; - } + while (stack.Count > 0) + { + var (currentBefore, currentAfter) = stack.Pop(); + if (!areEquivalentSingleLevel(currentBefore, currentAfter)) + return false; + } - if (before.RawKind != after.RawKind) - { - return false; + return true; } - - if (before.IsToken) + finally { - Debug.Assert(after.IsToken); - return AreTokensEquivalent(before, after, ignoreChildNode); + stack.Free(); } - if (topLevel) + bool areEquivalentSingleLevel(GreenNode? before, GreenNode? after) { - // Once we get down to the body level we don't need to go any further and we can - // consider these trees equivalent. - switch ((SyntaxKind)before.RawKind) + if (before == after) { - case SyntaxKind.Block: - case SyntaxKind.ArrowExpressionClause: - return AreNullableDirectivesEquivalent(before, after, ignoreChildNode); + return true; } - // If we're only checking top level equivalence, then we don't have to go down into - // the initializer for a field. However, we can't put that optimization for all - // fields. For example, fields that are 'const' do need their initializers checked as - // changing them can affect binding results. - if ((SyntaxKind)before.RawKind == SyntaxKind.FieldDeclaration) + if (before == null || after == null) { - var fieldBefore = (Green.FieldDeclarationSyntax)before; - var fieldAfter = (Green.FieldDeclarationSyntax)after; - - var isConstBefore = fieldBefore.Modifiers.Any((int)SyntaxKind.ConstKeyword); - var isConstAfter = fieldAfter.Modifiers.Any((int)SyntaxKind.ConstKeyword); + return false; + } - if (!isConstBefore && !isConstAfter) - { - ignoreChildNode = childKind => childKind == SyntaxKind.EqualsValueClause; - } + if (before.RawKind != after.RawKind) + { + return false; } - // NOTE(cyrusn): Do we want to avoid going down into attribute expressions? I don't - // think we can avoid it as there are likely places in the compiler that use these - // expressions. For example, if the user changes [InternalsVisibleTo("goo")] to - // [InternalsVisibleTo("bar")] then that must count as a top level change as it - // affects symbol visibility. Perhaps we could enumerate the places in the compiler - // that use the values inside source attributes and we can check if we're in an - // attribute with that name. It wouldn't be 100% correct (because of annoying things - // like using aliases), but would likely be good enough for the incremental cases in - // the IDE. - } + if (before.IsToken) + { + Debug.Assert(after.IsToken); + return AreTokensEquivalent(before, after, ignoreChildNode); + } - if (ignoreChildNode != null) - { - var e1 = before.ChildNodesAndTokens().GetEnumerator(); - var e2 = after.ChildNodesAndTokens().GetEnumerator(); - while (true) + if (topLevel) { - GreenNode? child1 = null; - GreenNode? child2 = null; + // Once we get down to the body level we don't need to go any further and we can + // consider these trees equivalent. + switch ((SyntaxKind)before.RawKind) + { + case SyntaxKind.Block: + case SyntaxKind.ArrowExpressionClause: + return AreNullableDirectivesEquivalent(before, after, ignoreChildNode); + } - // skip ignored children: - while (e1.MoveNext()) + // If we're only checking top level equivalence, then we don't have to go down into + // the initializer for a field. However, we can't put that optimization for all + // fields. For example, fields that are 'const' do need their initializers checked as + // changing them can affect binding results. + if ((SyntaxKind)before.RawKind == SyntaxKind.FieldDeclaration) { - var c = e1.Current; - if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + var fieldBefore = (Green.FieldDeclarationSyntax)before; + var fieldAfter = (Green.FieldDeclarationSyntax)after; + + var isConstBefore = fieldBefore.Modifiers.Any((int)SyntaxKind.ConstKeyword); + var isConstAfter = fieldAfter.Modifiers.Any((int)SyntaxKind.ConstKeyword); + + if (!isConstBefore && !isConstAfter) { - child1 = c; - break; + ignoreChildNode = static childKind => childKind == SyntaxKind.EqualsValueClause; } } - while (e2.MoveNext()) + // NOTE(cyrusn): Do we want to avoid going down into attribute expressions? I don't + // think we can avoid it as there are likely places in the compiler that use these + // expressions. For example, if the user changes [InternalsVisibleTo("goo")] to + // [InternalsVisibleTo("bar")] then that must count as a top level change as it + // affects symbol visibility. Perhaps we could enumerate the places in the compiler + // that use the values inside source attributes and we can check if we're in an + // attribute with that name. It wouldn't be 100% correct (because of annoying things + // like using aliases), but would likely be good enough for the incremental cases in + // the IDE. + } + + if (ignoreChildNode != null) + { + var e1 = before.ChildNodesAndTokens().GetEnumerator(); + var e2 = after.ChildNodesAndTokens().GetEnumerator(); + while (true) { - var c = e2.Current; - if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + GreenNode? child1 = null; + GreenNode? child2 = null; + + // skip ignored children: + while (e1.MoveNext()) { - child2 = c; - break; + var c = e1.Current; + if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + { + child1 = c; + break; + } } - } - if (child1 == null || child2 == null) - { - // false if some children remained - return child1 == child2; - } + while (e2.MoveNext()) + { + var c = e2.Current; + if (c != null && (c.IsToken || !ignoreChildNode((SyntaxKind)c.RawKind))) + { + child2 = c; + break; + } + } - if (!AreEquivalentRecursive(child1, child2, ignoreChildNode, topLevel)) - { - return false; - } - } - } - else - { - // simple comparison - not ignoring children + if (child1 == null || child2 == null) + { + // false if some children remained + return child1 == child2; + } - int slotCount = before.SlotCount; - if (slotCount != after.SlotCount) - { - return false; + stack.Push((child1, child2)); + } } - - for (int i = 0; i < slotCount; i++) + else { - var child1 = before.GetSlot(i); - var child2 = after.GetSlot(i); + // simple comparison - not ignoring children - if (!AreEquivalentRecursive(child1, child2, ignoreChildNode, topLevel)) + int slotCount = before.SlotCount; + if (slotCount != after.SlotCount) { return false; } + + // Walk the children backwards so that we can push them onto the stack and continue walking in DFS order. + for (var i = slotCount - 1; i >= 0; i--) + { + var child1 = before.GetSlot(i); + var child2 = after.GetSlot(i); + stack.Push((child1, child2)); + } } + // So far these are equivalent. Continue checking the children. return true; } } From 0c115e781b98d4ac0945040b9c2bf4395df37af5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:32:10 -0700 Subject: [PATCH 042/292] Simplify --- src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs index a41829fe01a4d..c74dc14c0f644 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs @@ -108,8 +108,8 @@ private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, { while (stack.Count > 0) { - var (currentBefore, currentAfter) = stack.Pop(); - if (!areEquivalentSingleLevel(currentBefore, currentAfter)) + (before, after) = stack.Pop(); + if (!areEquivalentSingleLevel()) return false; } @@ -120,7 +120,7 @@ private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, stack.Free(); } - bool areEquivalentSingleLevel(GreenNode? before, GreenNode? after) + bool areEquivalentSingleLevel() { if (before == after) { From e2cf02f58cfbddf9ab263c93f6a9dba34b9c2a04 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:36:36 -0700 Subject: [PATCH 043/292] Docs --- src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs index c74dc14c0f644..85030f5a7f841 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs @@ -101,6 +101,7 @@ private static bool AreTokensEquivalent(GreenNode? before, GreenNode? after, Fun private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, Func? ignoreChildNode, bool topLevel) { + // Use an explicit stack so we can walk down deep trees without blowing the real stack. var stack = ArrayBuilder<(GreenNode? before, GreenNode? after)>.GetInstance(); stack.Push((before, after)); From c2e2e8f7b235b0e2aaf6c1ba654b9cf35546b7b7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Apr 2024 15:41:07 -0700 Subject: [PATCH 044/292] simplify --- src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs index 85030f5a7f841..cba16e079df44 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxEquivalence.cs @@ -107,10 +107,9 @@ private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, try { - while (stack.Count > 0) + while (stack.TryPop(out var current)) { - (before, after) = stack.Pop(); - if (!areEquivalentSingleLevel()) + if (!areEquivalentSingleLevel(current.before, current.after)) return false; } @@ -121,7 +120,7 @@ private static bool AreEquivalentRecursive(GreenNode? before, GreenNode? after, stack.Free(); } - bool areEquivalentSingleLevel() + bool areEquivalentSingleLevel(GreenNode? before, GreenNode? after) { if (before == after) { From 6291b1c0cc666f0ee441bbe542a1df32f028a637 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Thu, 11 Apr 2024 12:12:48 -0700 Subject: [PATCH 045/292] Restore `dynamic` as result type of some operations involving `dynamic` arguments (#72964) Fixes #72750. For C# 12 language version: behavior of the compiler in regards to deciding between whether binding should be static or dynamic is the same as behavior of C# 12 compiler. As a result, for affected scenarios, what used to have `dynamic` type in C# 12 compiler will have `dynamic` type when C# 12 language version is targeted. For newer language versions: invocations statically bound in presence of dynamic arguments should have dynamic result if their dynamic binding succeeded in C# 12. Corresponding spec update - dotnet/csharplang#8027 at commit 8. Related to #33011, #72906, #72912, #72913, #72914, #72916, #72931. --- .../Portable/Binder/Binder.ValueChecks.cs | 30 +- .../Portable/Binder/Binder_Deconstruct.cs | 63 +- .../Portable/Binder/Binder_Expressions.cs | 56 +- .../Portable/Binder/Binder_Invocation.cs | 86 +- .../Portable/Binder/Binder_Operators.cs | 78 +- .../Portable/Binder/Binder_Statements.cs | 9 +- .../Compilation/CSharpSemanticModel.cs | 5 +- .../Portable/FlowAnalysis/NullableWalker.cs | 155 +- .../LocalRewriter_AssignmentOperator.cs | 61 +- .../LocalRewriter/LocalRewriter_Call.cs | 26 +- ...ocalRewriter_CompoundAssignmentOperator.cs | 7 +- .../LocalRewriter/LocalRewriter_Conversion.cs | 6 +- ...writer_DeconstructionAssignmentOperator.cs | 42 +- .../LocalRewriter/LocalRewriter_Event.cs | 9 +- .../LocalRewriter_IndexerAccess.cs | 26 +- .../LocalRewriter_LocalDeclaration.cs | 1 - ...writer_NullCoalescingAssignmentOperator.cs | 12 +- ...ObjectOrCollectionInitializerExpression.cs | 17 +- .../LocalRewriter_UnaryOperator.cs | 204 +- .../LocalRewriter_UsingStatement.cs | 2 +- .../Lowering/SyntheticBoundNodeFactory.cs | 16 +- ...perationTests_IObjectCreationExpression.cs | 36 + .../Test/Semantic/Semantics/DynamicTests.cs | 7007 ++++++++++++++++- .../Test/Semantic/Semantics/OperatorTests.cs | 10 +- ...nticModelGetSemanticInfoTests_LateBound.cs | 6 +- 25 files changed, 7696 insertions(+), 274 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 002b0a25e6cb1..48a9ac8a41427 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -310,8 +310,19 @@ private static bool RequiresRefOrOut(BindValueKind kind) #nullable enable - private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundIndexerAccess indexerAccess, BindValueKind valueKind, BindingDiagnosticBag diagnostics) + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// This flag and the assertion below help catch any new assignment scenarios and + /// make them aware of this subtlety. + /// The flag itself doesn't affect semantic analysis beyond the assertion. + /// + private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundIndexerAccess indexerAccess, BindValueKind valueKind, BindingDiagnosticBag diagnostics, bool dynamificationOfAssignmentResultIsHandled = false) { + Debug.Assert((valueKind & BindValueKind.Assignable) == 0 || (valueKind & BindValueKind.RefersToLocation) != 0 || dynamificationOfAssignmentResultIsHandled); + var useSetAccessor = valueKind == BindValueKind.Assignable && !indexerAccess.Indexer.ReturnsByRef; var accessorForDefaultArguments = useSetAccessor ? indexerAccess.Indexer.GetOwnOrInheritedSetMethod() @@ -404,15 +415,26 @@ private BoundIndexerAccess BindIndexerDefaultArgumentsAndParamsCollection(BoundI /// method returns a BoundBadExpression node. The method returns the original /// expression without generating any error if the expression has errors. /// - private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, BindingDiagnosticBag diagnostics) + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// This flag and the assertion below help catch any new assignment scenarios and + /// make them aware of this subtlety. + /// The flag itself doesn't affect semantic analysis beyond the assertion. + /// + private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, BindingDiagnosticBag diagnostics, bool dynamificationOfAssignmentResultIsHandled = false) { + Debug.Assert((valueKind & BindValueKind.Assignable) == 0 || (valueKind & BindValueKind.RefersToLocation) != 0 || dynamificationOfAssignmentResultIsHandled); + switch (expr.Kind) { case BoundKind.PropertyGroup: expr = BindIndexedPropertyAccess((BoundPropertyGroup)expr, mustHaveAllOptionalParameters: false, diagnostics: diagnostics); if (expr is BoundIndexerAccess indexerAccess) { - expr = BindIndexerDefaultArgumentsAndParamsCollection(indexerAccess, valueKind, diagnostics); + expr = BindIndexerDefaultArgumentsAndParamsCollection(indexerAccess, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: dynamificationOfAssignmentResultIsHandled); } break; @@ -430,7 +452,7 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind return expr; case BoundKind.IndexerAccess: - expr = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)expr, valueKind, diagnostics); + expr = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)expr, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: dynamificationOfAssignmentResultIsHandled); break; case BoundKind.UnconvertedObjectCreationExpression: diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 0e9c9f8b4bcb7..9f8e70e818d03 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -128,7 +128,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( var type = boundRHS.Type ?? voidType; return new BoundDeconstructionAssignmentOperator( node, - DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: true), + DeconstructionVariablesAsTuple(left, checkedVariables, assignmentResultTupleType: out _, diagnostics, ignoreDiagnosticsFromTuple: true), new BoundConversion(boundRHS.Syntax, boundRHS, Conversion.Deconstruction, @checked: false, explicitCastInCode: false, conversionGroupOpt: null, constantValueOpt: null, type: type, hasErrors: true), resultIsUsed, @@ -154,9 +154,9 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( FailRemainingInferences(checkedVariables, diagnostics); - var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); + var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, out NamedTypeSymbol returnType, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); Debug.Assert(hasErrors || lhsTuple.Type is object); - TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type!; + returnType = hasErrors ? CreateErrorType() : returnType; var boundConversion = new BoundConversion( boundRHS.Syntax, @@ -316,8 +316,8 @@ private bool MakeDeconstructionConversion( } else { - var single = variable.Single; - Debug.Assert(single is object); + Debug.Assert(variable.Single is object); + var single = AdjustAssignmentTargetForDynamic(variable.Single, forceDynamicResult: out _); Debug.Assert(single.Type is not null); CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); nestedConversion = this.Conversions.ClassifyConversionFromType(tupleOrDeconstructedTypes[i], single.Type, isChecked: CheckOverflowAtRuntime, ref useSiteInfo); @@ -502,7 +502,7 @@ private string GetDebuggerDisplay() if ((object?)variable.Single.Type != null) { // typed-variable on the left - mergedType = variable.Single.Type; + mergedType = AdjustAssignmentTargetForDynamic(variable.Single, forceDynamicResult: out _).Type; } } } @@ -542,11 +542,14 @@ private string GetDebuggerDisplay() syntax: syntax); } - private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder variables, + private BoundTupleExpression DeconstructionVariablesAsTuple( + CSharpSyntaxNode syntax, ArrayBuilder variables, + out NamedTypeSymbol assignmentResultTupleType, BindingDiagnosticBag diagnostics, bool ignoreDiagnosticsFromTuple) { int count = variables.Count; var valuesBuilder = ArrayBuilder.GetInstance(count); + var resultTypesWithAnnotationsBuilder = ArrayBuilder.GetInstance(count); var typesWithAnnotationsBuilder = ArrayBuilder.GetInstance(count); var locationsBuilder = ArrayBuilder.GetInstance(count); var namesBuilder = ArrayBuilder.GetInstance(count); @@ -554,18 +557,24 @@ private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syn foreach (var variable in variables) { BoundExpression value; + TypeSymbol resultType; if (variable.NestedVariables is object) { - value = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, diagnostics, ignoreDiagnosticsFromTuple); + value = DeconstructionVariablesAsTuple(variable.Syntax, variable.NestedVariables, out NamedTypeSymbol nestedResultType, diagnostics, ignoreDiagnosticsFromTuple); + resultType = nestedResultType; namesBuilder.Add(null); } else { Debug.Assert(variable.Single is object); value = variable.Single; + Debug.Assert(value.Type is not null); + resultType = value.Type; + value = AdjustAssignmentTargetForDynamic(value, forceDynamicResult: out _); namesBuilder.Add(ExtractDeconstructResultElementName(value)); } valuesBuilder.Add(value); + resultTypesWithAnnotationsBuilder.Add(TypeWithAnnotations.Create(resultType)); typesWithAnnotationsBuilder.Add(TypeWithAnnotations.Create(value.Type)); locationsBuilder.Add(variable.Syntax.Location); } @@ -579,14 +588,39 @@ private BoundTupleExpression DeconstructionVariablesAsTuple(CSharpSyntaxNode syn ImmutableArray inferredPositions = tupleNames.IsDefault ? default : tupleNames.SelectAsArray(n => n != null); bool disallowInferredNames = this.Compilation.LanguageVersion.DisallowInferredTupleElementNames(); + ImmutableArray elementLocations = locationsBuilder.ToImmutableAndFree(); + var createTupleDiagnostics = ignoreDiagnosticsFromTuple ? null : BindingDiagnosticBag.GetInstance(diagnostics); + var type = NamedTypeSymbol.CreateTuple( syntax.Location, - typesWithAnnotationsBuilder.ToImmutableAndFree(), locationsBuilder.ToImmutableAndFree(), + typesWithAnnotationsBuilder.ToImmutableAndFree(), elementLocations, + tupleNames, this.Compilation, + shouldCheckConstraints: createTupleDiagnostics is not null, + includeNullability: false, + errorPositions: disallowInferredNames ? inferredPositions : default, + syntax: syntax, diagnostics: createTupleDiagnostics); + + if (createTupleDiagnostics is { AccumulatesDiagnostics: true, DiagnosticBag: { } bag } && + bag.HasAnyResolvedErrors()) + { + diagnostics.AddRangeAndFree(createTupleDiagnostics); + createTupleDiagnostics = null; // Suppress possibly duplicate errors from CreateTuple call below. + } + + // This type is the same as the 'type' above, or differs only by using 'dynamic' for some elements. + assignmentResultTupleType = NamedTypeSymbol.CreateTuple( + syntax.Location, + resultTypesWithAnnotationsBuilder.ToImmutableAndFree(), elementLocations, tupleNames, this.Compilation, - shouldCheckConstraints: !ignoreDiagnosticsFromTuple, + shouldCheckConstraints: createTupleDiagnostics is not null, includeNullability: false, errorPositions: disallowInferredNames ? inferredPositions : default, - syntax: syntax, diagnostics: ignoreDiagnosticsFromTuple ? null : diagnostics); + syntax: syntax, diagnostics: createTupleDiagnostics); + + if (createTupleDiagnostics is not null) + { + diagnostics.AddRangeAndFree(createTupleDiagnostics); + } return (BoundTupleExpression)BindToNaturalType(new BoundTupleLiteral(syntax, arguments, tupleNames, inferredPositions, type), diagnostics); } @@ -788,12 +822,17 @@ private DeconstructionVariable BindDeconstructionVariables( } default: var boundVariable = BindExpression(node, diagnostics, invoked: false, indexed: false); - var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignable, diagnostics); + var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignable, diagnostics, dynamificationOfAssignmentResultIsHandled: true); + if (expression == null && checkedVariable.Kind != BoundKind.DiscardExpression) { expression = node; } + // This object doesn't escape BindDeconstruction method, we don't call AdjustAssignmentTargetForDynamic + // for checkedVariable here, instead we call it where binder accesses DeconstructionVariable.Single + // In some of the places we need to be able to detect the fact that the type used to be dynamic, and, + // if we erase the fact here, there will be no other place for us to look at. return new DeconstructionVariable(checkedVariable, node); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index ec89bd55c711e..0c95019c2627d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -233,10 +233,10 @@ internal NamedTypeSymbol CreateErrorType(string name = "") /// did not meet the requirements, the return value will be a that /// (typically) wraps the subexpression. /// - internal BoundExpression BindValue(ExpressionSyntax node, BindingDiagnosticBag diagnostics, BindValueKind valueKind) + internal BoundExpression BindValue(ExpressionSyntax node, BindingDiagnosticBag diagnostics, BindValueKind valueKind, bool dynamificationOfAssignmentResultIsHandled = false) { var result = this.BindExpression(node, diagnostics: diagnostics, invoked: false, indexed: false); - return CheckValue(result, valueKind, diagnostics); + return CheckValue(result, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled); } internal BoundExpression BindRValueWithoutTargetType(ExpressionSyntax node, BindingDiagnosticBag diagnostics, bool reportNoTargetType = true) @@ -5703,7 +5703,7 @@ private BoundExpression BindObjectInitializerMember( { // D = { ..., = , ... }, where D : dynamic boundMember = new BoundDynamicObjectInitializerMember(leftSyntax, memberName.Identifier.Text, implicitReceiver.Type, initializerType, hasErrors: false); - return CheckValue(boundMember, valueKind, diagnostics); + return CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); } else { @@ -5806,7 +5806,15 @@ private BoundExpression BindObjectInitializerMember( case BoundKind.IndexerAccess: { - var indexer = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)boundMember, valueKind, diagnostics); + var indexer = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); + + Debug.Assert(valueKind is BindValueKind.RValue or BindValueKind.RefAssignable or BindValueKind.Assignable); + + if (valueKind == BindValueKind.Assignable) + { + indexer = (BoundIndexerAccess)AdjustAssignmentTargetForDynamic(indexer, forceDynamicResult: out _); + } + boundMember = indexer; hasErrors |= isRhsNestedInitializer && !CheckNestedObjectInitializerPropertySymbol(indexer.Indexer, leftSyntax, diagnostics, hasErrors, ref resultKind); arguments = indexer.Arguments; @@ -5846,7 +5854,10 @@ private BoundExpression BindObjectInitializerMember( hasErrors |= !CheckNestedObjectInitializerPropertySymbol(property, leftSyntax, diagnostics, hasErrors, ref resultKind); } - return hasErrors ? boundMember : CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + return hasErrors ? + boundMember : + CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); case BoundKind.DynamicObjectInitializerMember: break; @@ -5863,7 +5874,8 @@ private BoundExpression BindObjectInitializerMember( case BoundKind.ArrayAccess: case BoundKind.PointerElementAccess: - return CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + return CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); default: return BadObjectInitializerMemberAccess(boundMember, implicitReceiver, leftSyntax, diagnostics, valueKind, hasErrors); @@ -5948,7 +5960,8 @@ private BoundExpression BadObjectInitializerMemberAccess( break; case LookupResultKind.Inaccessible: - boundMember = CheckValue(boundMember, valueKind, diagnostics); + Debug.Assert(boundMember is not BoundIndexerAccess); + boundMember = CheckValue(boundMember, valueKind, diagnostics, dynamificationOfAssignmentResultIsHandled: true); Debug.Assert(boundMember.HasAnyErrors); break; @@ -9719,13 +9732,14 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( argumentSyntax, singleCandidate); } } - else + // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for expanded non-array params cases. + else if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || IsMemberWithExpandedNonArrayParamsCollection(finalApplicableCandidates[0])) { var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); resultWithSingleCandidate.ResultsBuilder.Add(finalApplicableCandidates[0]); overloadResolutionResult.Free(); - return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, resultWithSingleCandidate, diagnostics); + return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, resultWithSingleCandidate, hasDynamicArgument: true, diagnostics); } } @@ -9741,7 +9755,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccess( return BindDynamicIndexer(syntax, receiver, analyzedArguments, finalApplicableCandidates.SelectAsArray(r => r.Member), diagnostics); } - return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, overloadResolutionResult, diagnostics); + return BindIndexerOrIndexedPropertyAccessContinued(syntax, receiver, propertyGroup, analyzedArguments, overloadResolutionResult, hasDynamicArgument: false, diagnostics); } private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( @@ -9750,6 +9764,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( ArrayBuilder propertyGroup, AnalyzedArguments analyzedArguments, OverloadResolutionResult overloadResolutionResult, + bool hasDynamicArgument, BindingDiagnosticBag diagnostics) { BoundExpression propertyAccess; @@ -9811,6 +9826,25 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( MemberResolutionResult resolutionResult = overloadResolutionResult.ValidResult; PropertySymbol property = resolutionResult.Member; + bool forceDynamicResultType = false; + var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + + // Due to backward compatibility, invocations statically bound in presence of dynamic arguments + // should have dynamic result if their dynamic binding succeeded in C# 12 and there are no + // obvious reasons for the runtime binder to fail (ref return, for example). + if (hasDynamicArgument && + !(property.Type.IsDynamic() || property.ReturnsByRef || + !Conversions.ClassifyConversionFromExpressionType(property.Type, Compilation.DynamicType, isChecked: false, ref useSiteInfo).IsImplicit || + IsMemberWithExpandedNonArrayParamsCollection(resolutionResult))) + { + var tryDynamicAccessDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false); + BindDynamicIndexer(syntax, receiver, analyzedArguments, ImmutableArray.Create(property), tryDynamicAccessDiagnostics); + forceDynamicResultType = !tryDynamicAccessDiagnostics.HasAnyResolvedErrors(); + tryDynamicAccessDiagnostics.Free(); + } + + diagnostics.Add(syntax, useSiteInfo); + ReportDiagnosticsIfObsolete(diagnostics, property, syntax, hasBaseReceiver: receiver != null && receiver.Kind == BoundKind.BaseReference); // Make sure that the result of overload resolution is valid. @@ -9841,7 +9875,7 @@ private BoundExpression BindIndexerOrIndexedPropertyAccessContinued( expanded: resolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm, argsToParams, defaultArguments: default, - property.Type, + forceDynamicResultType ? Compilation.DynamicType : property.Type, gotError); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 3b7c36fb9997c..948ce167d500b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -638,14 +638,19 @@ private BoundExpression BindDelegateInvocation( result = BindDynamicInvocation(node, boundExpression, analyzedArguments, overloadResolutionResult.GetAllApplicableMembers(), diagnostics, queryClause); } + // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for expanded non-array params cases. + else if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || IsMemberWithExpandedNonArrayParamsCollection(applicable)) + { + result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, hasDynamicArgument: true, boundExpression, diagnostics, queryClause); + } else { - result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, queryClause); + result = BindDynamicInvocation(node, boundExpression, analyzedArguments, overloadResolutionResult.GetAllApplicableMembers(), diagnostics, queryClause); } } else { - result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, queryClause); + result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, hasDynamicArgument: false, boundExpression, diagnostics, queryClause); } overloadResolutionResult.Free(); @@ -682,6 +687,13 @@ private bool HasApplicableMemberWithPossiblyExpandedNonArrayParamsCollection(MemberResolutionResult candidate) + where TMember : Symbol + { + return candidate.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm && + !candidate.Member.GetParameters().Last().Type.IsSZArray(); + } + private BoundExpression BindMethodGroupInvocation( SyntaxNode syntax, SyntaxNode expression, @@ -756,7 +768,7 @@ private BoundExpression BindMethodGroupInvocation( // we want to force any unbound lambda arguments to cache an appropriate conversion if possible; see 9448. result = BindInvocationExpressionContinued( syntax, expression, methodName, resolution.OverloadResolutionResult, resolution.AnalyzedArguments, - resolution.MethodGroup, delegateTypeOpt: null, diagnostics: BindingDiagnosticBag.Discarded, queryClause: queryClause); + resolution.MethodGroup, delegateTypeOpt: null, hasDynamicArgument: false, methodGroup, diagnostics: BindingDiagnosticBag.Discarded, queryClause: queryClause); } // Since the resolution is non-empty and has no diagnostics, the LookupResultKind in its MethodGroup is uninteresting. @@ -820,7 +832,7 @@ private BoundExpression BindMethodGroupInvocation( { result = BindInvocationExpressionContinued( syntax, expression, methodName, resolution.OverloadResolutionResult, resolution.AnalyzedArguments, - resolution.MethodGroup, delegateTypeOpt: null, diagnostics: diagnostics, queryClause: queryClause); + resolution.MethodGroup, delegateTypeOpt: null, hasDynamicArgument: false, methodGroup, diagnostics: diagnostics, queryClause: queryClause); } } } @@ -975,23 +987,33 @@ private BoundExpression TryEarlyBindSingleCandidateInvocationWithDynamicArgument return null; } - var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); - resultWithSingleCandidate.ResultsBuilder.Add(methodResolutionResult); + // For C# 12 and earlier statically bind invocations in presence of dynamic arguments only for local functions or expanded non-array params cases. + if (Compilation.LanguageVersion > LanguageVersion.CSharp12 || + singleCandidate.MethodKind == MethodKind.LocalFunction || + IsMemberWithExpandedNonArrayParamsCollection(methodResolutionResult)) + { + var resultWithSingleCandidate = OverloadResolutionResult.GetInstance(); + resultWithSingleCandidate.ResultsBuilder.Add(methodResolutionResult); - BoundExpression result = BindInvocationExpressionContinued( - node: syntax, - expression: expression, - methodName: methodName, - result: resultWithSingleCandidate, - analyzedArguments: resolution.AnalyzedArguments, - methodGroup: resolution.MethodGroup, - delegateTypeOpt: null, - diagnostics: diagnostics, - queryClause: queryClause); + BoundExpression result = BindInvocationExpressionContinued( + node: syntax, + expression: expression, + methodName: methodName, + result: resultWithSingleCandidate, + analyzedArguments: resolution.AnalyzedArguments, + methodGroup: resolution.MethodGroup, + delegateTypeOpt: null, + hasDynamicArgument: true, + boundMethodGroup, + diagnostics: diagnostics, + queryClause: queryClause); - resultWithSingleCandidate.Free(); + resultWithSingleCandidate.Free(); - return result; + return result; + } + + return null; } private ImmutableArray> GetCandidatesPassingFinalValidation( @@ -1130,6 +1152,8 @@ private BoundCall BindInvocationExpressionContinued( AnalyzedArguments analyzedArguments, MethodGroup methodGroup, NamedTypeSymbol delegateTypeOpt, + bool hasDynamicArgument, + BoundExpression targetMethodGroupOrDelegateInstance, BindingDiagnosticBag diagnostics, CSharpSyntaxNode queryClause = null) { @@ -1205,12 +1229,32 @@ private BoundCall BindInvocationExpressionContinued( GetOriginalMethods(result), methodGroup.ResultKind, methodGroup.TypeArguments.ToImmutable(), analyzedArguments, invokedAsExtensionMethod: invokedAsExtensionMethod, isDelegate: ((object)delegateTypeOpt != null)); } - // Otherwise, there were no dynamic arguments and overload resolution found a unique best candidate. + // Otherwise, overload resolution found a unique best candidate. // We still have to determine if it passes final validation. var methodResult = result.ValidResult; var returnType = methodResult.Member.ReturnType; var method = methodResult.Member; + bool forceDynamicResultType = false; + + var useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + + // Due to backward compatibility, invocations statically bound in presence of dynamic arguments + // should have dynamic result if their dynamic binding succeeded in C# 12 and there are no + // obvious reasons for the runtime binder to fail (ref return, for example). + if (hasDynamicArgument && + !(methodGroup.IsExtensionMethodGroup || method.MethodKind == MethodKind.LocalFunction || + method.ReturnsVoid || method.ReturnsByRef || returnType.IsDynamic() || + !Conversions.ClassifyConversionFromExpressionType(returnType, Compilation.DynamicType, isChecked: false, ref useSiteInfo).IsImplicit || + IsMemberWithExpandedNonArrayParamsCollection(methodResult))) + { + var tryDynamicInvocationDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false); + BindDynamicInvocation(node, targetMethodGroupOrDelegateInstance, analyzedArguments, ImmutableArray.Create(method), tryDynamicInvocationDiagnostics, queryClause); + forceDynamicResultType = !tryDynamicInvocationDiagnostics.HasAnyResolvedErrors(); + tryDynamicInvocationDiagnostics.Free(); + } + + diagnostics.Add(node, useSiteInfo); // It is possible that overload resolution succeeded, but we have chosen an // instance method and we're in a static method. A careful reading of the @@ -1340,7 +1384,9 @@ private BoundCall BindInvocationExpressionContinued( return new BoundCall(node, receiver, initialBindingReceiverIsSubjectToCloning: ReceiverIsSubjectToCloning(receiver, method), method, args, argNames, argRefKinds, isDelegateCall: isDelegateCall, expanded: expanded, invokedAsExtensionMethod: invokedAsExtensionMethod, - argsToParamsOpt: argsToParams, defaultArguments, resultKind: LookupResultKind.Viable, type: returnType, hasErrors: gotError); + argsToParamsOpt: argsToParams, defaultArguments, resultKind: LookupResultKind.Viable, + type: forceDynamicResultType ? Compilation.DynamicType : returnType, + hasErrors: gotError); } #nullable enable diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 40acd645365d9..1b22a794af71d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -22,7 +22,7 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, { node.Left.CheckDeconstructionCompatibleArgument(diagnostics); - BoundExpression left = BindValue(node.Left, diagnostics, GetBinaryAssignmentKind(node.Kind())); + BoundExpression left = BindValue(node.Left, diagnostics, GetBinaryAssignmentKind(node.Kind()), dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(left, diagnostics); BoundExpression right = BindValue(node.Right, diagnostics, BindValueKind.RValue); BinaryOperatorKind kind = SyntaxKindToBinaryOperatorKind(node.Kind()); @@ -43,6 +43,8 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, } } + left = AdjustAssignmentTargetForDynamic(left, out bool forceDynamicResult); + if (left.HasAnyErrors || right.HasAnyErrors) { // NOTE: no overload resolution candidates. @@ -81,7 +83,7 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, finalPlaceholder: placeholder, finalConversion: conversion, LookupResultKind.Viable, - left.Type, + AdjustAssignmentTypeToDynamicIfNecessary(left.Type, forceDynamicResult), hasErrors: false); } else @@ -244,7 +246,56 @@ private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, var leftConversion = CreateConversion(node.Left, leftPlaceholder, best.LeftConversion, isCast: false, conversionGroupOpt: null, best.Signature.LeftType, diagnostics); return new BoundCompoundAssignmentOperator(node, bestSignature, left, rightConverted, - leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, resultKind, originalUserDefinedOperators, leftType, hasError); + leftPlaceholder, leftConversion, finalPlaceholder, finalConversion, resultKind, originalUserDefinedOperators, AdjustAssignmentTypeToDynamicIfNecessary(left.Type, forceDynamicResult), hasError); + } + + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// + /// This helper takes care of the "reverting back to the indexer's type for the target" part. + /// See for the helper for the second part. + /// + private static BoundExpression AdjustAssignmentTargetForDynamic(BoundExpression target, out bool forceDynamicResult) + { + if (target is BoundIndexerAccess { Type.TypeKind: TypeKind.Dynamic, Indexer.Type.TypeKind: not TypeKind.Dynamic } indexerAccess) + { + Debug.Assert(!indexerAccess.Indexer.ReturnsByRef); + forceDynamicResult = true; + target = indexerAccess.Update( + indexerAccess.ReceiverOpt, + indexerAccess.InitialBindingReceiverIsSubjectToCloning, + indexerAccess.Indexer, + indexerAccess.Arguments, + indexerAccess.ArgumentNamesOpt, + indexerAccess.ArgumentRefKindsOpt, + indexerAccess.Expanded, + indexerAccess.ArgsToParamsOpt, + indexerAccess.DefaultArguments, + indexerAccess.Indexer.Type); + } + else + { + forceDynamicResult = false; + } + + return target; + } + + /// + /// When an indexer is accessed with dynamic argument is resolved statically, + /// in some scenarios its result type is set to 'dynamic' type. + /// Assignments to such indexers should be bound statically as well, reverting back + /// to the indexer's type for the target and setting result type of the assignment to 'dynamic' type. + /// + /// This helper takes care of the "setting result type of the assignment to 'dynamic' type" part. + /// See helper for the first part. + /// + TypeSymbol AdjustAssignmentTypeToDynamicIfNecessary(TypeSymbol leftType, bool forceDynamicResult) + { + return forceDynamicResult ? Compilation.DynamicType : leftType; } /// @@ -2261,7 +2312,9 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS { operandSyntax.CheckDeconstructionCompatibleArgument(diagnostics); - BoundExpression operand = BindToNaturalType(BindValue(operandSyntax, diagnostics, BindValueKind.IncrementDecrement), diagnostics); + BoundExpression operand = BindToNaturalType(BindValue(operandSyntax, diagnostics, BindValueKind.IncrementDecrement, dynamificationOfAssignmentResultIsHandled: true), + diagnostics); + UnaryOperatorKind kind = SyntaxKindToUnaryOperatorKind(node.Kind()); // If the operand is bad, avoid generating cascading errors. @@ -2283,6 +2336,8 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS hasErrors: true); } + operand = AdjustAssignmentTargetForDynamic(operand, out bool forceDynamicResult); + // The operand has to be a variable, property or indexer, so it must have a type. var operandType = operand.Type; Debug.Assert((object)operandType != null); @@ -2369,7 +2424,7 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS resultConversion, resultKind, originalUserDefinedOperators, - operandType, + AdjustAssignmentTypeToDynamicIfNecessary(operandType, forceDynamicResult), hasErrors); } @@ -4134,8 +4189,11 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio { MessageID.IDS_FeatureCoalesceAssignmentExpression.CheckFeatureAvailability(diagnostics, node.OperatorToken); - BoundExpression leftOperand = BindValue(node.Left, diagnostics, BindValueKind.CompoundAssignment); + BoundExpression leftOperand = BindValue(node.Left, diagnostics, BindValueKind.CompoundAssignment, dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(leftOperand, diagnostics); + + leftOperand = AdjustAssignmentTargetForDynamic(leftOperand, out bool forceDynamicResult); + BoundExpression rightOperand = BindValue(node.Right, diagnostics, BindValueKind.RValue); // If either operand is bad, bail out preventing more cascading errors @@ -4170,7 +4228,9 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio { diagnostics.Add(node, useSiteInfo); var convertedRightOperand = CreateConversion(rightOperand, underlyingRightConversion, underlyingLeftType, diagnostics); - return new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, underlyingLeftType); + var result = new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, AdjustAssignmentTypeToDynamicIfNecessary(underlyingLeftType, forceDynamicResult)); + Debug.Assert(result.IsNullableValueTypeAssignment); + return result; } } @@ -4184,7 +4244,9 @@ private BoundExpression BindNullCoalescingAssignmentOperator(AssignmentExpressio if (rightConversion.Exists) { var convertedRightOperand = CreateConversion(rightOperand, rightConversion, leftType, diagnostics); - return new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, leftType); + var result = new BoundNullCoalescingAssignmentOperator(node, leftOperand, convertedRightOperand, AdjustAssignmentTypeToDynamicIfNecessary(leftType, forceDynamicResult)); + Debug.Assert(!result.IsNullableValueTypeAssignment); + return result; } // a and b are incompatible and a compile-time error occurs diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 23271214ea97a..5fbaa109f9bed 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1429,9 +1429,11 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD if (isRef) MessageID.IDS_FeatureRefReassignment.CheckFeatureAvailability(diagnostics, node.Right.GetFirstToken()); - var op1 = BindValue(node.Left, diagnostics, lhsKind); + var op1 = BindValue(node.Left, diagnostics, lhsKind, dynamificationOfAssignmentResultIsHandled: true); ReportSuppressionIfNeeded(op1, diagnostics); + op1 = AdjustAssignmentTargetForDynamic(op1, out bool forceDynamicResult); + var rhsKind = isRef ? GetRequiredRHSValueKindForRefAssignment(op1) : BindValueKind.RValue; var op2 = BindValue(rhsExpr, diagnostics, rhsKind); @@ -1442,7 +1444,10 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, BindingD op1 = InferTypeForDiscardAssignment((BoundDiscardExpression)op1, op2, diagnostics); } - return BindAssignment(node, op1, op2, isRef, diagnostics); + BoundAssignmentOperator result = BindAssignment(node, op1, op2, isRef, diagnostics); + result = result.Update(result.Left, result.Right, result.IsRef, AdjustAssignmentTypeToDynamicIfNecessary(result.Type, forceDynamicResult)); + + return result; } private static BindValueKind GetRequiredRHSValueKindForRefAssignment(BoundExpression boundLeft) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index df844fd007afa..4a54eece1b5e2 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -3847,9 +3847,10 @@ private static void GetSymbolsAndResultKind(BoundIncrementOperator increment, ou { Debug.Assert((object)increment.MethodOpt == null && increment.OriginalUserDefinedOperatorsOpt.IsDefaultOrEmpty); UnaryOperatorKind op = increment.OperatorKind.Operator(); - symbols = OneOrMany.Create(new SynthesizedIntrinsicOperatorSymbol(increment.Operand.Type.StrippedType(), + TypeSymbol opType = increment.Operand.Type.StrippedType(); + symbols = OneOrMany.Create(new SynthesizedIntrinsicOperatorSymbol(opType, OperatorFacts.UnaryOperatorNameFromOperatorKind(op, isChecked: increment.OperatorKind.IsChecked()), - increment.Type.StrippedType())); + opType)); resultKind = increment.ResultKind; } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index bf4e997c62729..eb65524243bd8 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4081,7 +4081,8 @@ void setAnalyzedNullabilityAsContinuation( if (symbol != null) { - Debug.Assert(TypeSymbol.Equals(objectInitializer.Type, symbol.GetTypeOrReturnType().Type, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); + Debug.Assert(TypeSymbol.Equals(objectInitializer.Type, symbol.GetTypeOrReturnType().Type, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes) || + (symbol is PropertySymbol { IsIndexer: true } && objectInitializer.Type.IsDynamic())); symbol = AsMemberOfType(containingType, symbol); } @@ -5440,20 +5441,35 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly LearnFromNonNullTest(leftOperand, ref leftState); LearnFromNullTest(leftOperand, ref this.State); + var adjustedNodeType = node.Type; + if (LocalRewriter.ShouldConvertResultOfAssignmentToDynamic(node, leftOperand)) + { + Debug.Assert(leftOperand.Type is not null); + + if (node.IsNullableValueTypeAssignment) + { + adjustedNodeType = leftOperand.Type.GetNullableUnderlyingType(); + } + else + { + adjustedNodeType = leftOperand.Type; + } + } + // If we are assigning to a nullable value type variable, set the top-level state of // the LHS first, then change the slot to the Value property of the LHS to simulate // assignment of the RHS and update the nullable state of the underlying value type. if (node.IsNullableValueTypeAssignment) { Debug.Assert(targetType.Type.ContainsErrorType() || - node.Type?.ContainsErrorType() == true || - TypeSymbol.Equals(targetType.Type.GetNullableUnderlyingType(), node.Type, TypeCompareKind.AllIgnoreOptions)); + adjustedNodeType?.ContainsErrorType() == true || + TypeSymbol.Equals(targetType.Type.GetNullableUnderlyingType(), adjustedNodeType, TypeCompareKind.AllIgnoreOptions)); if (leftSlot > 0) { SetState(ref this.State, leftSlot, NullableFlowState.NotNull); leftSlot = GetNullableOfTValueSlot(targetType.Type, leftSlot, out _); } - targetType = TypeWithAnnotations.Create(node.Type, NullableAnnotation.NotAnnotated); + targetType = TypeWithAnnotations.Create(adjustedNodeType, NullableAnnotation.NotAnnotated); } TypeWithState rightResult = VisitOptionalImplicitConversion(rightOperand, targetType, useLegacyWarnings: UseLegacyWarnings(leftOperand), trackMembers: false, AssignmentKind.Assignment); @@ -5462,10 +5478,49 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly Join(ref this.State, ref leftState); TypeWithState resultType = TypeWithState.Create(targetType.Type, rightResult.State); - SetResultType(node, resultType); + + if (adjustedNodeType != (object?)node.Type) + { + Debug.Assert(adjustedNodeType is not null); + SetDynamicResult(node, resultType); + } + else + { + SetResultType(node, resultType); + } + return null; } + /// + /// When an operation on an indexer with dynamic argument is resolved statically, + /// in some scenarios result type of the operation is set to 'dynamic' type. + /// + /// This helper takes care of the setting result type to 'dynamic' type. + /// + private void SetDynamicResult(BoundExpression node, TypeWithState sourceType) + { + Debug.Assert(node.Type is not null); + Debug.Assert(node.Type.IsDynamic()); + Debug.Assert(sourceType.Type is not null); + Debug.Assert(!sourceType.Type.IsDynamic()); + Debug.Assert(!sourceType.Type.IsVoidType()); + + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + + SetResultType(node, + VisitConversion( + conversionOpt: null, + conversionOperand: node, + _conversions.ClassifyConversionFromExpressionType(sourceType.Type, node.Type, isChecked: false, ref discardedUseSiteInfo), + targetTypeWithNullability: TypeWithAnnotations.Create(node.Type, NullableAnnotation.Annotated), + operandType: sourceType, + checkConversion: false, + fromExplicitCast: false, + useLegacyWarnings: false, + AssignmentKind.Assignment)); + } + public override BoundNode? VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { Debug.Assert(!IsConditionalState); @@ -6054,7 +6109,7 @@ private static BoundExpression CreatePlaceholderIfNecessary(BoundExpression expr VisitResult? extensionReceiverResult = null; while (true) { - ReinferMethodAndVisitArguments(node, receiverType, firstArgumentResult: extensionReceiverResult); + reinferMethodAndVisitArguments(node, receiverType, firstArgumentResult: extensionReceiverResult); receiver = node; if (!calls.TryPop(out node!)) @@ -6090,7 +6145,7 @@ private static BoundExpression CreatePlaceholderIfNecessary(BoundExpression expr else { TypeWithState receiverType = visitAndCheckReceiver(node); - ReinferMethodAndVisitArguments(node, receiverType); + reinferMethodAndVisitArguments(node, receiverType); } return null; @@ -6129,35 +6184,44 @@ TypeWithState visitAndCheckReceiver(BoundCall node) return receiverType; } - } - private void ReinferMethodAndVisitArguments(BoundCall node, TypeWithState receiverType, VisitResult? firstArgumentResult = null) - { - var method = node.Method; - ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt; - if (!receiverType.HasNullType) + void reinferMethodAndVisitArguments(BoundCall node, TypeWithState receiverType, VisitResult? firstArgumentResult = null) { - // Update method based on inferred receiver type. - method = (MethodSymbol)AsMemberOfType(receiverType.Type, method); - } + var method = node.Method; + ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt; + if (!receiverType.HasNullType) + { + // Update method based on inferred receiver type. + method = (MethodSymbol)AsMemberOfType(receiverType.Type, method); + } - ImmutableArray results; - bool returnNotNull; - (method, results, returnNotNull) = VisitArguments(node, node.Arguments, refKindsOpt, method!.Parameters, node.ArgsToParamsOpt, node.DefaultArguments, - node.Expanded, node.InvokedAsExtensionMethod, method, firstArgumentResult: firstArgumentResult); + ImmutableArray results; + bool returnNotNull; + (method, results, returnNotNull) = VisitArguments(node, node.Arguments, refKindsOpt, method!.Parameters, node.ArgsToParamsOpt, node.DefaultArguments, + node.Expanded, node.InvokedAsExtensionMethod, method, firstArgumentResult: firstArgumentResult); - ApplyMemberPostConditions(node.ReceiverOpt, method); + ApplyMemberPostConditions(node.ReceiverOpt, method); - LearnFromEqualsMethod(method, node, receiverType, results); + LearnFromEqualsMethod(method, node, receiverType, results); - var returnState = GetReturnTypeWithState(method); - if (returnNotNull) - { - returnState = returnState.WithNotNullState(); - } + var returnState = GetReturnTypeWithState(method); + if (returnNotNull) + { + returnState = returnState.WithNotNullState(); + } + + if (node.Type.IsDynamic() && !node.Method.ReturnType.IsDynamic()) + { + Debug.Assert(!node.Method.ReturnsByRef); + SetDynamicResult(node, returnState); + } + else + { + SetResult(node, returnState, method.ReturnTypeWithAnnotations); + } - SetResult(node, returnState, method.ReturnTypeWithAnnotations); - SetUpdatedSymbol(node, node.Method, method); + SetUpdatedSymbol(node, node.Method, method); + } } private void LearnFromEqualsMethod(MethodSymbol method, BoundCall node, TypeWithState receiverType, ImmutableArray results) @@ -9778,7 +9842,7 @@ private void VisitThisOrBaseReference(BoundExpression node) } else { - SetResult(node, TypeWithState.Create(leftLValueType.Type, rightState.State), leftLValueType); + SetResultConveringToDynamicIfNecessary(node, left, TypeWithState.Create(leftLValueType.Type, rightState.State), leftLValueType); } AdjustSetValue(left, ref rightState); @@ -9788,6 +9852,21 @@ private void VisitThisOrBaseReference(BoundExpression node) return null; } + private void SetResultConveringToDynamicIfNecessary(BoundExpression originalAssignment, BoundExpression assignmentTarget, TypeWithState resultType, TypeWithAnnotations lvalueType) + { + Debug.Assert(originalAssignment.Type is not null); + Debug.Assert(assignmentTarget.Type is not null); + + if (LocalRewriter.ShouldConvertResultOfAssignmentToDynamic(originalAssignment, assignmentTarget)) + { + SetDynamicResult(originalAssignment, resultType); + } + else + { + SetResult(originalAssignment, resultType, lvalueType); + } + } + private bool IsPropertyOutputMoreStrictThanInput(PropertySymbol property) { var type = property.TypeWithAnnotations; @@ -10297,7 +10376,7 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress { var op = node.OperatorKind.Operator(); TypeWithState resultType = (op == UnaryOperatorKind.PrefixIncrement || op == UnaryOperatorKind.PrefixDecrement) ? resultOfIncrementType : operandType; - SetResultType(node, resultType); + SetResultConveringToDynamicIfNecessary(node, node.Operand, resultType, resultType.ToTypeWithAnnotations(compilation)); setResult = true; TrackNullableStateForAssignment(node, targetType: operandLvalue, targetSlot: MakeSlot(node.Operand), valueType: resultOfIncrementType); @@ -10365,7 +10444,7 @@ private ImmutableArray GetDeconstructionRightParts(BoundExpress // Handle `[DisallowNull]` on LHS operand (final assignment target). CheckDisallowedNullAssignment(resultTypeWithState, leftArgumentAnnotations, node.Syntax); - SetResultType(node, resultTypeWithState); + SetResultConveringToDynamicIfNecessary(node, node.Left, resultTypeWithState, resultTypeWithState.ToTypeWithAnnotations(compilation)); AdjustSetValue(node.Left, ref resultTypeWithState); Debug.Assert(MakeSlot(node) == -1); @@ -10503,7 +10582,17 @@ private TypeWithAnnotations GetDeclaredParameterResult(ParameterSymbol parameter VisitArguments(node, node.Arguments, node.ArgumentRefKindsOpt, indexer, node.ArgsToParamsOpt, node.DefaultArguments, node.Expanded); var resultType = ApplyUnconditionalAnnotations(indexer.TypeWithAnnotations.ToTypeWithState(), GetRValueAnnotations(indexer)); - SetResult(node, resultType, indexer.TypeWithAnnotations); + + if (node.Type.IsDynamic() && !node.Indexer.Type.IsDynamic()) + { + Debug.Assert(!node.Indexer.ReturnsByRef); + SetDynamicResult(node, resultType); + } + else + { + SetResult(node, resultType, indexer.TypeWithAnnotations); + } + SetUpdatedSymbol(node, node.Indexer, indexer); return null; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs index ee4a1091d7b32..9fcab11c2c2f6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs @@ -81,14 +81,56 @@ private BoundExpression VisitAssignmentOperator(BoundAssignmentOperator node, bo break; } - return MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, node.Type, used); + var result = MakeStaticAssignmentOperator(node.Syntax, loweredLeft, loweredRight, node.IsRef, used); + + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, left, result, used); + + Debug.Assert(used || result.Type?.IsVoidType() == true || + (left switch { BoundIndexerAccess indexer => indexer.Indexer, BoundPropertyAccess property => property.PropertySymbol, _ => null }) is not PropertySymbol prop || + prop.GetOwnOrInheritedSetMethod() is null); + + Debug.Assert(result.Type?.IsVoidType() == true || TypeSymbol.Equals(result.Type, node.Type, TypeCompareKind.AllIgnoreOptions)); + + return result; + } + + private static bool ShouldConvertResultOfAssignmentToDynamic(TypeSymbol? assignmentResultType, BoundExpression target) + { + if (assignmentResultType?.IsDynamic() == true && target is BoundIndexerAccess { Type.TypeKind: not TypeKind.Dynamic } indexerAccess) + { + Debug.Assert(!indexerAccess.Indexer.Type.IsDynamic()); + Debug.Assert(!indexerAccess.Indexer.ReturnsByRef); + + return true; + } + + return false; + } + + internal static bool ShouldConvertResultOfAssignmentToDynamic(BoundExpression assignment, BoundExpression target) + { + Debug.Assert(assignment is BoundAssignmentOperator or BoundIncrementOperator or BoundCompoundAssignmentOperator or BoundNullCoalescingAssignmentOperator); + return ShouldConvertResultOfAssignmentToDynamic(assignment.Type, target); + } + + private BoundExpression ConvertResultOfAssignmentToDynamicIfNecessary(BoundExpression originalAssignment, BoundExpression originalTarget, BoundExpression result, bool used) + { + Debug.Assert(originalAssignment.Type is not null); + if (used && ShouldConvertResultOfAssignmentToDynamic(originalAssignment, originalTarget)) + { + Debug.Assert(result.Type is not null); + Debug.Assert(!result.Type.IsDynamic()); + result = _factory.Convert(originalAssignment.Type, result); + } + + return result; } /// /// Generates a lowered form of the assignment operator for the given left and right sub-expressions. /// Left and right sub-expressions must be in lowered form. /// - private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, TypeSymbol type, + private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, bool used, bool isChecked, bool isCompoundAssignment) { switch (rewrittenLeft.Kind) @@ -132,7 +174,7 @@ private BoundExpression MakeAssignmentOperator(SyntaxNode syntax, BoundExpressio throw ExceptionUtilities.Unreachable(); default: - return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, isRef: false, type: type, used: used); + return MakeStaticAssignmentOperator(syntax, rewrittenLeft, rewrittenRight, isRef: false, used: used); } } @@ -168,7 +210,6 @@ private BoundExpression MakeStaticAssignmentOperator( BoundExpression rewrittenLeft, BoundExpression rewrittenRight, bool isRef, - TypeSymbol type, bool used) { switch (rewrittenLeft.Kind) @@ -193,7 +234,6 @@ private BoundExpression MakeStaticAssignmentOperator( false, default(ImmutableArray), rewrittenRight, - type, used); } @@ -214,7 +254,6 @@ private BoundExpression MakeStaticAssignmentOperator( indexerAccess.Expanded, indexerAccess.ArgsToParamsOpt, rewrittenRight, - type, used); } @@ -227,7 +266,6 @@ private BoundExpression MakeStaticAssignmentOperator( syntax, rewrittenLeft, rewrittenRight, - type, isRef); } @@ -252,9 +290,8 @@ private BoundExpression MakeStaticAssignmentOperator( sequence.Value, rewrittenRight, isRef, - type, used), - type); + sequence.Type); } goto default; @@ -264,8 +301,7 @@ private BoundExpression MakeStaticAssignmentOperator( return _factory.AssignmentExpression( syntax, rewrittenLeft, - rewrittenRight, - type); + rewrittenRight); } } } @@ -279,7 +315,6 @@ private BoundExpression MakePropertyAssignment( bool expanded, ImmutableArray argsToParamsOpt, BoundExpression rewrittenRight, - TypeSymbol type, bool used) { // Rewrite property assignment into call to setter. @@ -350,7 +385,7 @@ private BoundExpression MakePropertyAssignment( AppendToPossibleNull(argTemps, rhsTemp), ImmutableArray.Create(setterCall), boundRhs, - type); + rhsTemp.Type); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 7e7c8d253ac70..956ff4e6071c1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -410,13 +410,23 @@ BoundExpression visitArgumentsAndFinishRewrite(BoundCall node, BoundExpression? Instrumenter.InterceptCallAndAdjustArguments(ref method, ref rewrittenReceiver, ref rewrittenArguments, ref argRefKindsOpt); } - var rewrittenCall = MakeCall(node, node.Syntax, rewrittenReceiver, method, rewrittenArguments, argRefKindsOpt, node.ResultKind, node.Type, temps.ToImmutableAndFree()); + var rewrittenCall = MakeCall(node, node.Syntax, rewrittenReceiver, method, rewrittenArguments, argRefKindsOpt, node.ResultKind, temps.ToImmutableAndFree()); if (Instrument) { rewrittenCall = Instrumenter.InstrumentCall(node, rewrittenCall); } + if (node.Type.IsDynamic() && !method.ReturnType.IsDynamic()) + { + Debug.Assert(node.Type.IsDynamic()); + Debug.Assert(!method.ReturnsByRef); + Debug.Assert(rewrittenCall.Type is not null); + Debug.Assert(!rewrittenCall.Type.IsDynamic()); + Debug.Assert(!rewrittenCall.Type.IsVoidType()); + rewrittenCall = _factory.Convert(node.Type, rewrittenCall); + } + return rewrittenCall; } } @@ -429,7 +439,6 @@ private BoundExpression MakeCall( ImmutableArray rewrittenArguments, ImmutableArray argumentRefKinds, LookupResultKind resultKind, - TypeSymbol type, ImmutableArray temps) { BoundExpression rewrittenBoundCall; @@ -454,7 +463,7 @@ private BoundExpression MakeCall( resultKind, rewrittenArguments[0], rewrittenArguments[1], - type); + method.ReturnType); } else if (node == null) { @@ -472,7 +481,7 @@ private BoundExpression MakeCall( argsToParamsOpt: default(ImmutableArray), defaultArguments: default(BitVector), resultKind: resultKind, - type: type); + type: method.ReturnType); } else { @@ -489,9 +498,11 @@ private BoundExpression MakeCall( argsToParamsOpt: default(ImmutableArray), defaultArguments: default(BitVector), node.ResultKind, - node.Type); + method.ReturnType); } + Debug.Assert(rewrittenBoundCall.Type is not null); + if (!temps.IsDefaultOrEmpty) { return new BoundSequence( @@ -499,13 +510,13 @@ private BoundExpression MakeCall( locals: temps, sideEffects: ImmutableArray.Empty, value: rewrittenBoundCall, - type: type); + type: rewrittenBoundCall.Type); } return rewrittenBoundCall; } - private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenReceiver, MethodSymbol method, ImmutableArray rewrittenArguments, TypeSymbol type) + private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenReceiver, MethodSymbol method, ImmutableArray rewrittenArguments) { return MakeCall( node: null, @@ -515,7 +526,6 @@ private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenRe rewrittenArguments: rewrittenArguments, argumentRefKinds: default(ImmutableArray), resultKind: LookupResultKind.Viable, - type: type, temps: default); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs index c015096e3b21c..ed3c55336a5d1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs @@ -113,6 +113,10 @@ private BoundExpression VisitCompoundAssignmentOperator(BoundCompoundAssignmentO temps.Free(); stores.Free(); + + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, node.Left, result, used); + + Debug.Assert(used || node.Left is not (BoundIndexerAccess { Indexer.RefKind: RefKind.None } or BoundPropertyAccess { PropertySymbol.RefKind: RefKind.None }) || result.Type?.IsVoidType() == true); return result; BoundExpression rewriteAssignment(BoundExpression leftRead) @@ -153,7 +157,8 @@ BoundExpression rewriteAssignment(BoundExpression leftRead) RemovePlaceholderReplacement(node.FinalPlaceholder); } - return MakeAssignmentOperator(syntax, transformedLHS, opFinal, node.Left.Type, used: used, isChecked: isChecked, isCompoundAssignment: true); + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.Left.Type, TypeCompareKind.AllIgnoreOptions)); + return MakeAssignmentOperator(syntax, transformedLHS, opFinal, used: used, isChecked: isChecked, isCompoundAssignment: true); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index 635043ef290bd..4f6b4f96c378d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -1421,9 +1421,6 @@ private BoundExpression RewriteIntPtrConversion( rewrittenOperand = MakeConversionNode(rewrittenOperand, method.GetParameterType(0), @checked); - var returnType = method.ReturnType; - Debug.Assert((object)returnType != null); - if (_inExpressionLambda) { return BoundConversion.Synthesized(syntax, rewrittenOperand, conversion, @checked, explicitCastInCode: explicitCastInCode, conversionGroupOpt: null, constantValueOpt, rewrittenType); @@ -1433,8 +1430,7 @@ private BoundExpression RewriteIntPtrConversion( syntax: syntax, rewrittenReceiver: null, method: method, - rewrittenArguments: ImmutableArray.Create(rewrittenOperand), - type: returnType); + rewrittenArguments: ImmutableArray.Create(rewrittenOperand)); return MakeConversionNode(rewrittenCall, rewrittenType, @checked, markAsChecked: true); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs index dfbe8f214ae22..bb24b2810febb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs @@ -18,8 +18,8 @@ internal sealed partial class LocalRewriter { var right = node.Right; Debug.Assert(right.Conversion.Kind == ConversionKind.Deconstruction); - - return RewriteDeconstruction(node.Left, right.Conversion, right.Operand, node.IsUsed); + Debug.Assert(node.Type.IsTupleType); + return RewriteDeconstruction(node.Left, right.Conversion, right.Operand, node.IsUsed, (NamedTypeSymbol)node.Type); } /// @@ -34,13 +34,12 @@ internal sealed partial class LocalRewriter /// - the conversion phase /// - the assignment phase /// - private BoundExpression? RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed) + private BoundExpression? RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed, NamedTypeSymbol assignmentResultTupleType) { var lhsTemps = ArrayBuilder.GetInstance(); var lhsEffects = ArrayBuilder.GetInstance(); ArrayBuilder lhsTargets = GetAssignmentTargetsAndSideEffects(left, lhsTemps, lhsEffects); - Debug.Assert(left.Type is { }); - BoundExpression? result = RewriteDeconstruction(lhsTargets, conversion, left.Type, right, isUsed); + BoundExpression? result = RewriteDeconstruction(lhsTargets, conversion, right, assignmentResultTupleType, isUsed); Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets); if (result is null) { @@ -55,8 +54,8 @@ internal sealed partial class LocalRewriter private BoundExpression? RewriteDeconstruction( ArrayBuilder lhsTargets, Conversion conversion, - TypeSymbol leftType, BoundExpression right, + NamedTypeSymbol assignmentResultTupleType, bool isUsed) { if (right.Kind == BoundKind.ConditionalOperator) @@ -66,17 +65,17 @@ internal sealed partial class LocalRewriter return conditional.Update( conditional.IsRef, VisitExpression(conditional.Condition), - RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Consequence, isUsed: true)!, - RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Alternative, isUsed: true)!, + RewriteDeconstruction(lhsTargets, conversion, conditional.Consequence, assignmentResultTupleType, isUsed: true)!, + RewriteDeconstruction(lhsTargets, conversion, conditional.Alternative, assignmentResultTupleType, isUsed: true)!, conditional.ConstantValueOpt, - leftType, + assignmentResultTupleType, wasTargetTyped: true, - leftType); + assignmentResultTupleType); } var temps = ArrayBuilder.GetInstance(); var effects = DeconstructionSideEffects.GetInstance(); - BoundExpression? returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, isUsed, inInit: true); + BoundExpression? returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, assignmentResultTupleType, isUsed, inInit: true); reverseAssignmentsToTargetsIfApplicable(); effects.Consolidate(); @@ -213,6 +212,7 @@ static bool canReorderTargetAssignments(ArrayBuilder temps, DeconstructionSideEffects effects, + NamedTypeSymbol assignmentResultTupleType, bool isUsed, bool inInit) { @@ -227,14 +227,19 @@ static bool canReorderTargetAssignments(ArrayBuilder(removeDelegate), - type: clearMethod.ReturnType); + rewrittenArguments: ImmutableArray.Create(removeDelegate)); } else { @@ -163,8 +163,7 @@ private BoundExpression RewriteWindowsRuntimeEventAssignmentOperator(SyntaxNode syntax: syntax, rewrittenReceiver: null, method: marshalMethod, - rewrittenArguments: marshalArguments, - type: marshalMethod.ReturnType); + rewrittenArguments: marshalArguments); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs index 2009b7fb09125..d69554e9b960d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs @@ -91,7 +91,6 @@ private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftO node.Expanded, node.ArgsToParamsOpt, node.DefaultArguments, - node.Type, node, isLeftOfAssignment); } @@ -106,17 +105,22 @@ private BoundExpression MakeIndexerAccess( bool expanded, ImmutableArray argsToParamsOpt, BitVector defaultArguments, - TypeSymbol type, - BoundIndexerAccess? oldNodeOpt, + BoundExpression originalIndexerAccessOrObjectInitializerMember, bool isLeftOfAssignment) { + Debug.Assert(originalIndexerAccessOrObjectInitializerMember is BoundIndexerAccess or BoundObjectInitializerMember); + Debug.Assert(originalIndexerAccessOrObjectInitializerMember.Type is not null); + if (isLeftOfAssignment && indexer.RefKind == RefKind.None) { + TypeSymbol type = indexer.Type; + Debug.Assert(originalIndexerAccessOrObjectInitializerMember.Type.Equals(type, TypeCompareKind.ConsiderEverything)); + // This is an indexer set access. We return a BoundIndexerAccess node here. // This node will be rewritten with MakePropertyAssignment when rewriting the enclosing BoundAssignmentOperator. - return oldNodeOpt != null ? - oldNodeOpt.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type) : + return originalIndexerAccessOrObjectInitializerMember is BoundIndexerAccess indexerAccess ? + indexerAccess.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type) : new BoundIndexerAccess(syntax, rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, indexer, arguments, argumentNamesOpt, argumentRefKindsOpt, expanded, argsToParamsOpt, defaultArguments, type); } else @@ -145,6 +149,16 @@ private BoundExpression MakeIndexerAccess( BoundExpression call = MakePropertyGetAccess(syntax, rewrittenReceiver, indexer, rewrittenArguments, argumentRefKindsOpt, getMethod); + if (originalIndexerAccessOrObjectInitializerMember.Type.IsDynamic() == true && !indexer.Type.IsDynamic()) + { + Debug.Assert(call.Type is not null); + Debug.Assert(!call.Type.IsDynamic()); + Debug.Assert(!getMethod.ReturnsByRef); + call = _factory.Convert(originalIndexerAccessOrObjectInitializerMember.Type, call); + } + + Debug.Assert(call.Type is not null); + if (temps.Count == 0) { temps.Free(); @@ -157,7 +171,7 @@ private BoundExpression MakeIndexerAccess( temps.ToImmutableAndFree(), ImmutableArray.Empty, call, - type); + call.Type); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs index b2d42a9a8597e..7e24d7df4caa4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LocalDeclaration.cs @@ -63,7 +63,6 @@ internal sealed partial class LocalRewriter localSymbol.Type ), rewrittenInitializer, - localSymbol.Type, localSymbol.IsRef), hasErrors); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs index 851da6ffc4147..6b3b62aa0bc01 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_NullCoalescingAssignmentOperator.cs @@ -25,10 +25,12 @@ public override BoundNode VisitNullCoalescingAssignmentOperator(BoundNullCoalesc var lhsRead = MakeRValue(transformedLHS); BoundExpression loweredRight = VisitExpression(node.RightOperand); - return node.IsNullableValueTypeAssignment ? + var result = node.IsNullableValueTypeAssignment ? rewriteNullCoalescingAssignmentForValueType() : rewriteNullCoalscingAssignmentStandard(); + return ConvertResultOfAssignmentToDynamicIfNecessary(node, node.LeftOperand, result, used: true); + BoundExpression rewriteNullCoalscingAssignmentStandard() { // Now that LHS is transformed with temporaries, we rewrite this node into a coalesce expression: @@ -38,7 +40,8 @@ BoundExpression rewriteNullCoalscingAssignmentStandard() // isCompoundAssignment is only used for dynamic scenarios, and we want those scenarios to treat this like a standard assignment. // See CodeGenNullCoalescingAssignmentTests.CoalescingAssignment_DynamicRuntimeCastFailure, which will fail if // isCompoundAssignment is set to true. It will fail to throw a runtime binder cast exception. - BoundExpression assignment = MakeAssignmentOperator(syntax, transformedLHS, loweredRight, node.LeftOperand.Type, used: true, isChecked: false, isCompoundAssignment: false); + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.LeftOperand.Type, TypeCompareKind.AllIgnoreOptions)); + BoundExpression assignment = MakeAssignmentOperator(syntax, transformedLHS, loweredRight, used: true, isChecked: false, isCompoundAssignment: false); // lhsRead ?? (transformedLHS = loweredRight) var leftPlaceholder = new BoundValuePlaceholder(lhsRead.Syntax, lhsRead.Type); @@ -60,7 +63,6 @@ BoundExpression rewriteNullCoalscingAssignmentStandard() BoundExpression rewriteNullCoalescingAssignmentForValueType() { Debug.Assert(node.LeftOperand.Type.IsNullableType()); - Debug.Assert(node.Type.Equals(node.RightOperand.Type)); // We lower the expression to this form: // @@ -107,17 +109,17 @@ BoundExpression rewriteNullCoalescingAssignmentForValueType() temps.Add(tmp.LocalSymbol); // tmp = loweredRight; - var tmpAssignment = MakeAssignmentOperator(node.Syntax, tmp, loweredRight, node.Type, used: true, isChecked: false, isCompoundAssignment: false); + var tmpAssignment = MakeAssignmentOperator(node.Syntax, tmp, loweredRight, used: true, isChecked: false, isCompoundAssignment: false); Debug.Assert(transformedLHS.Type.GetNullableUnderlyingType().Equals(tmp.Type.StrippedType(), TypeCompareKind.AllIgnoreOptions)); // transformedLhs = tmp; + Debug.Assert(TypeSymbol.Equals(transformedLHS.Type, node.LeftOperand.Type, TypeCompareKind.AllIgnoreOptions)); var transformedLhsAssignment = MakeAssignmentOperator( node.Syntax, transformedLHS, MakeConversionNode(tmp, transformedLHS.Type, @checked: false, markAsChecked: true), - node.LeftOperand.Type, used: true, isChecked: false, isCompoundAssignment: false); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs index 5ca410e45cde8..8583c6cf6329a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectOrCollectionInitializerExpression.cs @@ -206,7 +206,7 @@ private BoundExpression MakeDynamicCollectionInitializer(BoundExpression rewritt Instrumenter.InterceptCallAndAdjustArguments(ref addMethod, ref rewrittenReceiver, ref rewrittenArguments, ref argumentRefKindsOpt); } - return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.ResultKind, addMethod.ReturnType, temps.ToImmutableAndFree()); + return MakeCall(null, syntax, rewrittenReceiver, addMethod, rewrittenArguments, argumentRefKindsOpt, initializer.ResultKind, temps.ToImmutableAndFree()); } private BoundExpression VisitObjectInitializerMember(BoundObjectInitializerMember node, ref BoundExpression rewrittenReceiver, ArrayBuilder sideEffects, ref ArrayBuilder? temps) @@ -363,7 +363,8 @@ private void AddObjectInitializer( { // Rewrite simple assignment to field/property. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: assignment.IsRef, assignment.Type, used: false)); + Debug.Assert(assignment.Type.IsDynamic() || TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: assignment.IsRef, used: false)); return; } } @@ -434,7 +435,8 @@ private void AddObjectInitializer( { // Rewrite simple assignment to field/property. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, used: false)); return; } @@ -466,7 +468,8 @@ private void AddObjectInitializer( { // Rewrite as simple assignment. var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, false, used: false)); return; } @@ -499,7 +502,8 @@ private void AddObjectInitializer( if (!isRhsNestedInitializer) { var rewrittenRight = VisitExpression(right); - result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: false, assignment.Type, used: false)); + Debug.Assert(TypeSymbol.Equals(rewrittenAccess.Type, assignment.Type, TypeCompareKind.AllIgnoreOptions)); + result.Add(MakeStaticAssignmentOperator(assignment.Syntax, rewrittenAccess, rewrittenRight, isRef: false, used: false)); return; } @@ -684,8 +688,7 @@ private BoundExpression MakeObjectInitializerMemberAccess( rewrittenLeft.Expanded, rewrittenLeft.ArgsToParamsOpt, rewrittenLeft.DefaultArguments, - type: propertySymbol.Type, - oldNodeOpt: null, + originalIndexerAccessOrObjectInitializerMember: rewrittenLeft, isLeftOfAssignment: !isRhsNestedInitializer); } else diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs index 5e3384761eba7..00d6f2d7854b0 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UnaryOperator.cs @@ -438,7 +438,8 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) BoundExpression transformedLHS = TransformCompoundAssignmentLHS(node.Operand, isRegularCompoundAssignment: true, tempInitializers, tempSymbols, isDynamic); TypeSymbol? operandType = transformedLHS.Type; //type of the variable being incremented Debug.Assert(operandType is { }); - Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2)); + Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2) || + ShouldConvertResultOfAssignmentToDynamic(node, node.Operand)); LocalSymbol tempSymbol = _factory.SynthesizedLocal(operandType); tempSymbols.Add(tempSymbol); @@ -452,7 +453,7 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) // prefix: (X)(T.Increment((T)operand))) // postfix: (X)(T.Increment((T)temp))) - var newValue = MakeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); + var newValue = makeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); // there are two strategies for completing the rewrite. // The reason is that indirect assignments read the target of the assignment before evaluating @@ -472,122 +473,131 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) // In a case of the non-byref operand we use a single-sequence strategy as it results in shorter // overall life time of temps and as such more appropriate. (problem of crossed reads does not affect that case) // - if (IsIndirectOrInstanceField(transformedLHS)) + BoundExpression result; + + if (isIndirectOrInstanceField(transformedLHS)) { - return RewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, operandType, boundTemp, newValue); + result = rewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); } else { - return RewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, operandType, boundTemp, newValue); + result = rewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue); } - } - - private static bool IsIndirectOrInstanceField(BoundExpression expression) - { - switch (expression.Kind) - { - case BoundKind.Local: - return ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None; - case BoundKind.Parameter: - Debug.Assert(!IsCapturedPrimaryConstructorParameter(expression)); - return ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None; + result = ConvertResultOfAssignmentToDynamicIfNecessary(node, node.Operand, result, used: true); + Debug.Assert(TypeSymbol.Equals(result.Type, node.Type, TypeCompareKind.AllIgnoreOptions)); - case BoundKind.FieldAccess: - return !((BoundFieldAccess)expression).FieldSymbol.IsStatic; - } + return result; - return false; - } + static bool isIndirectOrInstanceField(BoundExpression expression) + { + switch (expression.Kind) + { + case BoundKind.Local: + return ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None; - private BoundNode RewriteWithNotRefOperand( - bool isPrefix, - bool isChecked, - ArrayBuilder tempSymbols, - ArrayBuilder tempInitializers, - SyntaxNode syntax, - BoundExpression transformedLHS, - TypeSymbol operandType, - BoundExpression boundTemp, - BoundExpression newValue) - { - // prefix: temp = (X)(T.Increment((T)operand))); operand = temp; - // postfix: temp = operand; operand = (X)(T.Increment((T)temp))); - ImmutableArray assignments = ImmutableArray.Create( - MakeAssignmentOperator(syntax, boundTemp, isPrefix ? newValue : MakeRValue(transformedLHS), operandType, used: false, isChecked: isChecked, isCompoundAssignment: false), - MakeAssignmentOperator(syntax, transformedLHS, isPrefix ? boundTemp : newValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false)); - - // prefix: Seq( operand initializers; temp = (T)(operand + 1); operand = temp; result: temp) - // postfix: Seq( operand initializers; temp = operand; operand = (T)(temp + 1); result: temp) - return new BoundSequence( - syntax: syntax, - locals: tempSymbols.ToImmutableAndFree(), - sideEffects: tempInitializers.ToImmutableAndFree().Concat(assignments), - value: boundTemp, - type: operandType); - } + case BoundKind.Parameter: + Debug.Assert(!IsCapturedPrimaryConstructorParameter(expression)); + return ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None; - private BoundNode RewriteWithRefOperand( - bool isPrefix, - bool isChecked, - ArrayBuilder tempSymbols, - ArrayBuilder tempInitializers, - SyntaxNode syntax, - BoundExpression operand, - TypeSymbol operandType, - BoundExpression boundTemp, - BoundExpression newValue) - { - var tempValue = isPrefix ? newValue : MakeRValue(operand); - Debug.Assert(tempValue.Type is { }); - var tempAssignment = MakeAssignmentOperator(syntax, boundTemp, tempValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false); + case BoundKind.FieldAccess: + return !((BoundFieldAccess)expression).FieldSymbol.IsStatic; + } - var operandValue = isPrefix ? boundTemp : newValue; - var tempAssignedAndOperandValue = new BoundSequence( - syntax, - ImmutableArray.Empty, - ImmutableArray.Create(tempAssignment), - operandValue, - tempValue.Type); - - // prefix: operand = Seq{temp = (T)(operand + 1); temp;} - // postfix: operand = Seq{temp = operand; ; (T)(temp + 1);} - BoundExpression operandAssignment = MakeAssignmentOperator(syntax, operand, tempAssignedAndOperandValue, operandType, used: false, isChecked: isChecked, isCompoundAssignment: false); - - // prefix: Seq{operand initializers; operand = Seq{temp = (T)(operand + 1); temp;} result: temp} - // postfix: Seq{operand initializers; operand = Seq{temp = operand; ; (T)(temp + 1);} result: temp} - tempInitializers.Add(operandAssignment); - return new BoundSequence( - syntax: syntax, - locals: tempSymbols.ToImmutableAndFree(), - sideEffects: tempInitializers.ToImmutableAndFree(), - value: boundTemp, - type: operandType); - } + return false; + } - private BoundExpression MakeIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) - { - if (node.OperatorKind.IsDynamic()) + BoundExpression rewriteWithNotRefOperand( + bool isPrefix, + bool isChecked, + ArrayBuilder tempSymbols, + ArrayBuilder tempInitializers, + SyntaxNode syntax, + BoundExpression transformedLHS, + BoundExpression boundTemp, + BoundExpression newValue) { - return _dynamicFactory.MakeDynamicUnaryOperator(node.OperatorKind, rewrittenValueToIncrement, node.Type).ToExpression(); + Debug.Assert(boundTemp.Type is not null); + + // prefix: temp = (X)(T.Increment((T)operand))); operand = temp; + // postfix: temp = operand; operand = (X)(T.Increment((T)temp))); + ImmutableArray assignments = ImmutableArray.Create( + MakeAssignmentOperator(syntax, boundTemp, isPrefix ? newValue : MakeRValue(transformedLHS), used: false, isChecked: isChecked, isCompoundAssignment: false), + MakeAssignmentOperator(syntax, transformedLHS, isPrefix ? boundTemp : newValue, used: false, isChecked: isChecked, isCompoundAssignment: false)); + + // prefix: Seq( operand initializers; temp = (T)(operand + 1); operand = temp; result: temp) + // postfix: Seq( operand initializers; temp = operand; operand = (T)(temp + 1); result: temp) + return new BoundSequence( + syntax: syntax, + locals: tempSymbols.ToImmutableAndFree(), + sideEffects: tempInitializers.ToImmutableAndFree().Concat(assignments), + value: boundTemp, + type: boundTemp.Type); } - BoundExpression result; - if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.UserDefined) + BoundExpression rewriteWithRefOperand( + bool isPrefix, + bool isChecked, + ArrayBuilder tempSymbols, + ArrayBuilder tempInitializers, + SyntaxNode syntax, + BoundExpression operand, + BoundExpression boundTemp, + BoundExpression newValue) { - result = MakeUserDefinedIncrementOperator(node, rewrittenValueToIncrement); + Debug.Assert(boundTemp.Type is not null); + + var tempValue = isPrefix ? newValue : MakeRValue(operand); + Debug.Assert(tempValue.Type is { }); + var tempAssignment = MakeAssignmentOperator(syntax, boundTemp, tempValue, used: false, isChecked: isChecked, isCompoundAssignment: false); + + var operandValue = isPrefix ? boundTemp : newValue; + var tempAssignedAndOperandValue = new BoundSequence( + syntax, + ImmutableArray.Empty, + ImmutableArray.Create(tempAssignment), + operandValue, + tempValue.Type); + + // prefix: operand = Seq{temp = (T)(operand + 1); temp;} + // postfix: operand = Seq{temp = operand; ; (T)(temp + 1);} + BoundExpression operandAssignment = MakeAssignmentOperator(syntax, operand, tempAssignedAndOperandValue, used: false, isChecked: isChecked, isCompoundAssignment: false); + + // prefix: Seq{operand initializers; operand = Seq{temp = (T)(operand + 1); temp;} result: temp} + // postfix: Seq{operand initializers; operand = Seq{temp = operand; ; (T)(temp + 1);} result: temp} + tempInitializers.Add(operandAssignment); + return new BoundSequence( + syntax: syntax, + locals: tempSymbols.ToImmutableAndFree(), + sideEffects: tempInitializers.ToImmutableAndFree(), + value: boundTemp, + type: boundTemp.Type); } - else + + BoundExpression makeIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) { - result = MakeBuiltInIncrementOperator(node, rewrittenValueToIncrement); - } + if (node.OperatorKind.IsDynamic()) + { + return _dynamicFactory.MakeDynamicUnaryOperator(node.OperatorKind, rewrittenValueToIncrement, node.Type).ToExpression(); + } - // Generate the conversion back to the type of the original expression. + BoundExpression result; + if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.UserDefined) + { + result = MakeUserDefinedIncrementOperator(node, rewrittenValueToIncrement); + } + else + { + result = MakeBuiltInIncrementOperator(node, rewrittenValueToIncrement); + } - // (X)(short)((int)(short)x + 1) - result = ApplyConversionIfNotIdentity(node.ResultConversion, node.ResultPlaceholder, result); + // Generate the conversion back to the type of the original expression. - return result; + // (X)(short)((int)(short)x + 1) + result = ApplyConversionIfNotIdentity(node.ResultConversion, node.ResultPlaceholder, result); + + return result; + } } private BoundExpression ApplyConversionIfNotIdentity(BoundExpression? conversion, BoundValuePlaceholder? placeholder, BoundExpression replacement) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index 4997351f94f68..249c7b23c0f1e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -532,7 +532,7 @@ private BoundExpression MakeCallWithNoExplicitArgument(MethodArgumentInfo method ref temps, invokedAsExtensionMethod: method.IsExtensionMethod); - return MakeCall(null, syntax, expression, method, rewrittenArguments, argumentRefKindsOpt, LookupResultKind.Viable, method.ReturnType, temps.ToImmutableAndFree()); + return MakeCall(null, syntax, expression, method, rewrittenArguments, argumentRefKindsOpt, LookupResultKind.Viable, temps.ToImmutableAndFree()); } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index b94eddbaea342..ec3d3a6db7114 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -469,20 +469,20 @@ public BoundExpressionStatement ExpressionStatement(BoundExpression expr) /// public BoundExpression AssignmentExpression(BoundExpression left, BoundExpression right, bool isRef = false) { - Debug.Assert(left.Type is { } && right.Type is { } && - (left.Type.Equals(right.Type, TypeCompareKind.AllIgnoreOptions) || - StackOptimizerPass1.IsFixedBufferAssignmentToRefLocal(left, right, isRef) || - right.Type.IsErrorType() || left.Type.IsErrorType())); - - return AssignmentExpression(Syntax, left, right, left.Type, isRef: isRef, wasCompilerGenerated: true); + return AssignmentExpression(Syntax, left, right, isRef: isRef, wasCompilerGenerated: true); } /// /// Creates a general assignment that might be instrumented. /// - public BoundExpression AssignmentExpression(SyntaxNode syntax, BoundExpression left, BoundExpression right, TypeSymbol type, bool isRef = false, bool hasErrors = false, bool wasCompilerGenerated = false) + public BoundExpression AssignmentExpression(SyntaxNode syntax, BoundExpression left, BoundExpression right, bool isRef = false, bool hasErrors = false, bool wasCompilerGenerated = false) { - var assignment = new BoundAssignmentOperator(syntax, left, right, isRef, type, hasErrors) { WasCompilerGenerated = wasCompilerGenerated }; + Debug.Assert(left.Type is { } && right.Type is { } && + (left.Type.Equals(right.Type, TypeCompareKind.AllIgnoreOptions) || + StackOptimizerPass1.IsFixedBufferAssignmentToRefLocal(left, right, isRef) || + right.Type.IsErrorType() || left.Type.IsErrorType())); + + var assignment = new BoundAssignmentOperator(syntax, left, right, isRef, left.Type, hasErrors) { WasCompilerGenerated = wasCompilerGenerated }; return (InstrumentationState?.IsSuppressed == false && left is BoundLocal { LocalSymbol.SynthesizedKind: SynthesizedLocalKind.UserDefined } or BoundParameter) ? InstrumentationState.Instrumenter.InstrumentUserDefinedLocalAssignment(assignment) : diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs index ebde2d102e21d..a382338ea9d86 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs @@ -15000,6 +15000,42 @@ static void M(bool result) VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); } + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/72931")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72931")] + public void ObjectCreationFlow_75_CollectionInitializerError() + { + string source = @" +public class C +{ + public static void Main() + /**/{ + int d = 1; + var c = new C() { [d] = {2} }; + }/**/ + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + } +} + +class C2 +{ +} +"; + var expectedDiagnostics = new[] { + // (7,33): error CS1922: Cannot initialize type 'C2' with a collection initializer because it does not implement 'System.Collections.IEnumerable' + // var c = new C() { [d] = {2} }; + Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{2}").WithArguments("C2").WithLocation(7, 33) + }; + + string expectedFlowGraph = @" +"; + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); + } + [Fact] public void ObjectCreationExpression_NoNewConstraint() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs index c965d1742584e..b9cd381d376f6 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs @@ -13,11 +13,32 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { - public partial class SyntaxBinderTests + public partial class DynamicTests : CompilingTestBase { + private static void TestTypes(string source) + { + SyntaxBinderTests.TestTypes(source); + } + + private static void TestOperatorKinds(string source) + { + SyntaxBinderTests.TestOperatorKinds(source); + } + + private static void TestDynamicMemberAccessCore(string source) + { + SyntaxBinderTests.TestDynamicMemberAccessCore(source); + } + + private static void TestCompoundAssignment(string source) + { + SyntaxBinderTests.TestCompoundAssignment(source); + } + #region Conversions [Fact] @@ -3013,7 +3034,7 @@ public C1(int x){} public C1(long x){} } "; - CreateCompilationWithMscorlib40AndSystemCore(new[] { Parse(source, options: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp5)) }).VerifyDiagnostics( + CreateCompilationWithMscorlib40AndSystemCore(new[] { Parse(source, options: TestOptions.RegularPreview) }).VerifyDiagnostics( // (43,55): warning CS1981: Using 'is' to test compatibility with 'dynamic' is essentially identical to testing compatibility with 'Object' and will succeed for all non-null values // Expression> e18 = x => d is dynamic; // ok, warning @@ -3079,6 +3100,76 @@ public C1(long x){} // Expression> e24 = x => new C1(x); Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "new C1(x)").WithLocation(49, 55) ); + + CreateCompilationWithMscorlib40AndSystemCore(new[] { Parse(source, options: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp5)) }).VerifyDiagnostics( + + // (43,55): warning CS1981: Using 'is' to test compatibility with 'dynamic' is essentially identical to testing compatibility with 'Object' and will succeed for all non-null values + // Expression> e18 = x => d is dynamic; // ok, warning + Diagnostic(ErrorCode.WRN_IsDynamicIsConfusing, "d is dynamic").WithArguments("is", "dynamic", "Object").WithLocation(43, 55), + // (46,59): error CS8382: Invalid object creation + // Expression> e21 = x => new dynamic(); + Diagnostic(ErrorCode.ERR_InvalidObjectCreation, "dynamic").WithLocation(46, 59), + // (25,52): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e0 = () => new C { P = d }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(25, 52), + // (27,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e2 = () => new C { D = { X = { Y = 1 }, Z = 1 } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "X").WithLocation(27, 54), + // (27,60): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e2 = () => new C { D = { X = { Y = 1 }, Z = 1 } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "Y").WithLocation(27, 60), + // (27,69): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e2 = () => new C { D = { X = { Y = 1 }, Z = 1 } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "Z").WithLocation(27, 69), + // (28,44): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e3 = () => new C() { { d }, { d, d, d } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "{ d }").WithLocation(28, 44), + // (28,51): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e3 = () => new C() { { d }, { d, d, d } }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "{ d, d, d }").WithLocation(28, 51), + // (29,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e4 = x => x.goo(); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.goo()").WithLocation(29, 54), + // (29,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e4 = x => x.goo(); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.goo").WithLocation(29, 54), + // (30,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e5 = x => x[1]; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x[1]").WithLocation(30, 54), + // (31,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e6 = x => x.y.z; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.y.z").WithLocation(31, 54), + // (31,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e6 = x => x.y.z; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x.y").WithLocation(31, 54), + // (32,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e7 = x => x + 1; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "x + 1").WithLocation(32, 54), + // (33,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e8 = x => -x; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "-x").WithLocation(33, 54), + // (34,54): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e9 = x => f(d); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f(d)").WithLocation(34, 54), + // (36,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e11 = x => f((dynamic)1); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f((dynamic)1)").WithLocation(36, 55), + // (37,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e12 = x => f(d ?? null); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "f(d ?? null)").WithLocation(37, 55), + // (38,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e13 = x => d ? 1 : 2; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(38, 55), + // (39,56): error CS1989: Async lambda expressions cannot be converted to expression trees + // Expression>> e14 = async x => await d; + Diagnostic(ErrorCode.ERR_BadAsyncExpressionTree, "async x => await d").WithLocation(39, 56), + // (47,84): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e22 = x => from a in new[] { d } select a + 1; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "a + 1").WithLocation(47, 84), + // (49,55): error CS1963: An expression tree may not contain a dynamic operation + // Expression> e24 = x => new C1(x); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "new C1(x)").WithLocation(49, 55) + ); } [Fact, WorkItem(578401, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/578401")] @@ -4399,13 +4490,24 @@ static void Main() } "; - var comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.Regular7_2, options: TestOptions.DebugExe); + var comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); CompileAndVerify(comp, expectedOutput: @" True True ").VerifyDiagnostics(); + + comp = CreateCompilationWithMscorlib45AndCSharp(source, parseOptions: TestOptions.Regular7_2, options: TestOptions.DebugExe); + + comp.VerifyEmitDiagnostics( + // (10,15): error CS8364: Arguments with 'in' modifier cannot be used in dynamically dispatched expressions. + // M1(in d, d = 2, in d); + Diagnostic(ErrorCode.ERR_InDynamicMethodArg, "d").WithLocation(10, 15), + // (10,28): error CS8364: Arguments with 'in' modifier cannot be used in dynamically dispatched expressions. + // M1(in d, d = 2, in d); + Diagnostic(ErrorCode.ERR_InDynamicMethodArg, "d").WithLocation(10, 28) + ); } [WorkItem(22813, "https://github.com/dotnet/roslyn/issues/22813")] @@ -4545,5 +4647,6904 @@ class C2 : C1 CompileAndVerify(comp, expectedOutput: "int").VerifyDiagnostics(); } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_01(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1.Test(""name"", value)); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + object Test(string name, object value); +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.Test(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object? Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_02(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1.Test(""name"", value)); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + object Test(string name, object value); +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.Test(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int? Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("dynamic I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1.Test(""name"", value)); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + dynamic Test(string name, object value); +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => i1.Test(""name"", value)").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic? Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_Extension(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C().Test(""name"", d); + System.Console.Write(result); + } +} + +static class Extensions +{ + public static int Test(this C c, string name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"new C().Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_01() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(name: ""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(name: ""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_02() + { + string source1 = @" +#pragma warning disable //CS8500: This takes the address of, gets the size of, or declares a pointer to a managed type ('string') + +unsafe public class C +{ + static void Main() + { + string name = ""name""; + dynamic d = 1; + var result = Test(&name, d); + System.Console.Write(result); + } + + static int Test(string* name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(&name, d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 C.Test(System.String* name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value, params System.Collections.Generic.List list) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Collections.Generic.List list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value, params int[] list) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_VoidReturning() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var a = Test1(d); + } + + static void Test1(int x) {} +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (7,13): error CS0815: Cannot assign void to an implicitly-typed variable + // var a = Test1(d); + Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "a = Test1(d)").WithArguments("void").WithLocation(7, 13) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + Test1(d)++; + var a = Test1(d); + System.Console.WriteLine(a); + } + + static int _test1 = 0; + static ref int Test1(int x) => ref _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_NoConversion() + { + string source = @" +unsafe public class C +{ + public static void Main() + { + int v = 0; + _test1 = &v; + + dynamic d = 1; + (*Test1(d))++; + var a = Test1(d); + System.Console.WriteLine(*a); + } + + static int* _test1; + static int* Test1(int x) => _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_LocalFunction([CombinatorialValues(0, 12, 13)] int version) + { + var parseOptions = version switch { 12 => TestOptions.Regular12, 13 => TestOptions.RegularNext, _ => TestOptions.RegularPreview }; + + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var e = test4(d); + System.Console.WriteLine(e); + + static int test4(int x) => x; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "e").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 e", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_Delegate(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source = @" +public class C +{ + static C M(Test i1, dynamic value) + { + var result = i1(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +delegate object Test(string name, object value); + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T); +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object Test.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.TargetMethod.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Delegate_01() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(name: ""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value) => 123; + delegate int D(string name, object value); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(name: ""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Delegate_02() + { + string source1 = @" +#pragma warning disable //CS8500: This takes the address of, gets the size of, or declares a pointer to a managed type ('string') + +unsafe public class C +{ + static void Main() + { + string name = ""name""; + dynamic d = 1; + var result = Test(&name, d); + System.Console.Write(result); + } + + static D Test = (string* name, object value) => 123; + delegate int D(string* name, object value); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(&name, d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 C.D.Invoke(System.String* name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Delegate_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value, params System.Collections.Generic.List list) => 123; + delegate int D(string name, object value, params System.Collections.Generic.List list); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Collections.Generic.List list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Delegate() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value, params int[] list) => 123; + delegate int D(string name, object value, params int[] list); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_VoidReturning_Delegate() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var a = Test1(d); + } + + static D Test1 = null; +} + +delegate void D(int x); +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (7,13): error CS0815: Cannot assign void to an implicitly-typed variable + // var a = Test1(d); + Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "a = Test1(d)").WithArguments("void").WithLocation(7, 13) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Delegate() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + Test1(d)++; + var a = Test1(d); + System.Console.WriteLine(a); + } + + static int _test1 = 0; + static D Test1 = (int x) => ref _test1; + + delegate ref int D(int x); +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_NoConversion_Delegate() + { + string source = @" +unsafe public class C +{ + public static void Main() + { + int v = 0; + _test1 = &v; + + dynamic d = 1; + (*Test1(d))++; + var a = Test1(d); + System.Console.WriteLine(*a); + } + + static int* _test1; + static D Test1 = (int x) => _test1; + delegate int* D(int x); +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_Property_01(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Object I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1[""name"", value]); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + object this[string name, object value] {get;} +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.get_Item(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object? this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ResultIsDynamic_Property_02(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1[""name"", value]); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + int this[string name, object value] {get;} +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe, parseOptions: parseOptions); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => Convert(i1.get_Item(""name"", value)" + (ExecutionConditionUtil.IsMonoOrCoreClr ? ", Object)" : ")")).VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int? this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_03() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((I1 i1, dynamic value) => i1[""name"", value]); + Print(expr); + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(typeof(T3)); + System.Console.Write("" ""); + System.Console.Write(expr); + } +} + +public interface I1 +{ + dynamic this[string name, object value] {get;} +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + CompileAndVerify(comp2, + expectedOutput: @"System.Object (i1, value) => i1.get_Item(""name"", value)").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + dynamic? this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (9,46): warning CS8604: Possible null reference argument for parameter 'c' in 'C JsonSerializer.Deserialize(Stream c)'. + // return JsonSerializer.Deserialize(result); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "result").WithArguments("c", "C JsonSerializer.Deserialize(Stream c)").WithLocation(9, 46) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Property_01() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[name: ""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), operation.Property.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Property_02() + { + string source1 = @" +#pragma warning disable //CS8500: This takes the address of, gets the size of, or declares a pointer to a managed type ('string') + +unsafe public class C +{ + static void Main() + { + string name = ""name""; + dynamic d = 1; + var result = new C()[&name, d]; + System.Console.Write(result); + } + + int this[string* name, object value] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String* name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + CompileAndVerify(comp1, expectedOutput: "123", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [CombinatorialData] + public void SingleCandidate_ArgumentsNotSupportedByDynamic_Property_03(bool testPreview) + { + var parseOptions = testPreview ? TestOptions.RegularPreview : TestOptions.RegularNext; + + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value, params System.Collections.Generic.List list] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: parseOptions); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Collections.Generic.List list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Property() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value, params int[] list] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Int32[] list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + c[d]++; + var a = c[d]; + System.Console.WriteLine(a); + } + + int _test1 = 0; + ref int this[int x] => ref _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + TypeInfo typeInfo; + + foreach (var elementAccess in tree.GetRoot().DescendantNodes().OfType()) + { + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + } + + var increment = tree.GetRoot().DescendantNodes().OfType().Single(); + typeInfo = model.GetTypeInfo(increment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_NoConversion_Property() + { + string source = @" +unsafe public class C +{ + public static void Main() + { + int v = 0; + var c = new C(); + c._test1 = &v; + + dynamic d = 1; + (*c[d])++; + var a = c[d]; + System.Console.WriteLine(*a); + } + + int* _test1; + int* this[int x] => _test1; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true), targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32* a", symbolInfo.Symbol.ToTestDisplayString()); + + TypeInfo typeInfo; + + foreach (var elementAccess in tree.GetRoot().DescendantNodes().OfType()) + { + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32* C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32*", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32*", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + } + + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.Skipped).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = (int?)null; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = (int?)null; + Print(a); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic right = 2; + var a = c[d] = right; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic? right = (int?)null; + var a = c[d] = right; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (12,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(12, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + object o = null; + var c = new C(); + var a = c[d] = o; + System.Console.Write(a); + } + + System.IO.Stream this[int x] + { + get => null; + set {} + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.IO.Stream C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.IO.Stream", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.IO.Stream", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.IO.Stream", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.IO.Stream", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Object", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.IO.Stream", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (9,24): error CS0266: Cannot implicitly convert type 'object' to 'System.IO.Stream'. An explicit conversion exists (are you missing a cast?) + // var a = c[d] = o; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "o").WithArguments("object", "System.IO.Stream").WithLocation(9, 24) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] = (int?)null; + Print(a); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Addition(System.Int32 left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += (int?)null; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (10,17): warning CS0458: The result of the expression is always 'null' of type 'dynamic' + // var a = c[d] += (int?)null; + Diagnostic(ErrorCode.WRN_AlwaysNull, "c[d] += (int?)null").WithArguments("dynamic").WithLocation(10, 17), + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72906")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Addition(dynamic left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += (int?)null; + Print(a); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullable analysis behavior is consistent with how dynamic operators are analyzed. + // See https://github.com/dotnet/roslyn/issues/72906, for example. + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72906")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += d; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Addition(System.Int32 left, dynamic right)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "1 1").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + dynamic? right = null; + var c = new C(); + var a = c[d] += right; + Print(a); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullable analysis behavior is consistent with how dynamic operators are analyzed. + // See https://github.com/dotnet/roslyn/issues/72906, for example. + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CompoundAssignment_OperatorIsBoundStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + C2 right = new C2(); + var a = c[d] += right; + Print(a); + } + + C2 this[int x] + { + get => new C2(); + set {} + } + + static void Print(dynamic b) + { + } +} + +class C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + Assert.Null(symbolInfo.Symbol); + + var operation = (ICompoundAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("C2", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + Assert.True(operation.Type.IsErrorType()); + Assert.Null(operation.OperatorMethod); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (9,17): error CS0019: Operator '+=' cannot be applied to operands of type 'C2' and 'C2' + // var a = c[d] += right; + Diagnostic(ErrorCode.ERR_BadBinaryOps, "c[d] += right").WithArguments("+=", "C2", "C2").WithLocation(9, 17) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_CompoundAssignment() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Addition(System.Int32 left, System.Int32 right)", symbolInfo.Symbol.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] += (int?)null; + Print(a); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (10,17): warning CS0458: The result of the expression is always 'null' of type 'int?' + // var a = c[d] += (int?)null; + Diagnostic(ErrorCode.WRN_AlwaysNull, "c[d] += (int?)null").WithArguments("int?").WithLocation(10, 17), + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PostfixIncrement_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "2 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + } + + int? _test1 = 2; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PostfixIncrement_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Increment(dynamic value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "2 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + } + + int? _test1 = 2; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PostfixIncrement_OperatorIsBoundStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d]++; + Print(a); + } + + + C2 this[int x] + { + get => new C2(); + set {} + } + + static void Print(dynamic b) + { + } +} + +class C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + Assert.Equal(CodeAnalysis.NullableFlowState.None, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PostfixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + Assert.True(typeInfo.Type.IsErrorType()); + Assert.Equal(typeInfo.Type, typeInfo.ConvertedType); + symbolInfo = model.GetSymbolInfo(assignment); + Assert.Null(symbolInfo.Symbol); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + comp.VerifyDiagnostics( + // (8,17): error CS0023: Operator '++' cannot be applied to operand of type 'C2' + // var a = c[d]++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "c[d]++").WithArguments("++", "C2").WithLocation(8, 17) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PrefixIncrement_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "3 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + } + + int? _test1 = 2; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_PrefixIncrement_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("dynamic dynamic.op_Increment(dynamic value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "3 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + } + + int? _test1 = 2; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_PrefixIncrement() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 2; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (PrefixUnaryExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + symbolInfo = model.GetSymbolInfo(assignment); + AssertEx.Equal("System.Int32 System.Int32.op_Increment(System.Int32 value)", symbolInfo.Symbol.ToTestDisplayString()); + + var operation = (IIncrementOrDecrementOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + Assert.Null(operation.OperatorMethod); + + CompileAndVerify(comp, expectedOutput: "3 3").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ++c[d]; + Print(a); + } + + int? _test1 = 2; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= ""2""; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + string _test1 = null!; + string this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.String C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.String", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= (string?)null; + Print(a); + } + + string? _test1 = null; + string? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int? _test1 = null; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32? C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32?", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32?", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= (int?)null; + Print(a); + } + + int? _test1 = null; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= ""2""; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + string _test1 = null!; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= (string?)null; + Print(a); + } + + string? _test1 = null; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_04() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic right = ""2""; + var a = c[d] ??= right; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + string _test1 = null!; + string this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.String C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.String", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + dynamic? right = null; + var a = c[d] ??= right; + Print(a); + } + + string? _test1 = null; + string? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (12,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a").WithArguments("b", "void C.Print(dynamic b)").WithLocation(12, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72912")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_RightSideIsConvertedStatically() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= ""2""; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + C2? _test1 = null; + C2? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} + +class C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("C2? a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2? C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2?", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("C2?", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.String", typeInfo.ConvertedType.ToTestDisplayString()); + + // The unexpected nullability warning is pre-existing condition - https://github.com/dotnet/roslyn/issues/72912 + comp.VerifyDiagnostics( + // (10,17): error CS0019: Operator '??=' cannot be applied to operands of type 'C2' and 'string' + // var a = c[d] ??= "2"; + Diagnostic(ErrorCode.ERR_BadBinaryOps, @"c[d] ??= ""2""").WithArguments("??=", "C2", "string").WithLocation(10, 17), + // (10,26): warning CS8619: Nullability of reference types in value of type 'string' doesn't match target type 'C2'. + // var a = c[d] ??= "2"; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, @"""2""").WithArguments("string", "C2").WithLocation(10, 26) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_ConditionalAssignment_Error() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= new C2(); + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + C2 _test1; + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} + +struct C2 {} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("? a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("?", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("C2", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("?", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (8,17): error CS0019: Operator '??=' cannot be applied to operands of type 'C2' and 'C2' + // var a = c[d] ??= new C2(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "c[d] ??= new C2()").WithArguments("??=", "C2", "C2").WithLocation(8, 17) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_ConditionalAssignment() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = c[d] ??= 2; + Print(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int? _test1 = null; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + System.Console.Write(b); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 a", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32? C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32?", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32?", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Print(expr); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Print(expr); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = 2 }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_03() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + dynamic v = 2; + var c = new C() { [d] = v }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Print(expr); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,70): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 70), + // (9,71): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 71), + // (9,76): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d, dynamic v) => new C() { [d] = v }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "v").WithLocation(9, 76) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_Assignment_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = ""2"" }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.String", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + comp.VerifyDiagnostics( + // (7,33): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var c = new C() { [d] = "2" }; + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""2""").WithArguments("string", "int").WithLocation(7, 33) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72916")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_Assignment() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = 2 }; + System.Console.WriteLine(c[1]); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + // IInvalidOperation is pre-existing condition - https://github.com/dotnet/roslyn/issues/72916 + var propertyRef = (IInvalidOperation)model.GetOperation(elementAccess); + //var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + //AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IAssignmentOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Int32", operation.Target.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Value.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", operation.Type.ToTestDisplayString()); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_ObjectInitializer_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("C2 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,67): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_ObjectInitializer_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,67): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "F").WithLocation(9, 67) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_ObjectInitializer() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + System.Console.WriteLine(c[1].F); + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref C2 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("C2", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Print(expr); + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} + +class C2 +{ + public int F = 0; +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,59): error CS8153: An expression tree lambda may not contain a call to a method, property, or indexer that returns by reference + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_RefReturningCallInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = { F = 2 } }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = { F = 2 } }; + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (7,35): error CS0117: 'C2' does not contain a definition for 'F' + // var c = new C() { [d] = { F = 2 } }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "F").WithArguments("C2", "F").WithLocation(7, 35) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_CollectionInitializer_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + System.Collections.Generic.List this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Collections.Generic.List C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + System.Collections.Generic.List this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,66): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + C2 this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_MemberInitializer_CollectionInitializer_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60), + // (9,66): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "2").WithLocation(9, 66) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + CompileAndVerify(comp3).VerifyDiagnostics(); + } + + [ConditionalFact(typeof(NoIOperationValidation))] // IOperation validation is suppressed due to https://github.com/dotnet/roslyn/issues/72931 + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72931")] + public void SingleCandidate_RefReturning_Property_MemberInitializer_CollectionInitializer() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + System.Console.WriteLine(c[1][0]); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + ref System.Collections.Generic.List this[int x] + { + get => ref _test1; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + var symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Collections.Generic.List C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var assignment = (AssignmentExpressionSyntax)elementAccess.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Collections.Generic.List", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IMemberInitializerOperation)model.GetOperation(assignment); + AssertEx.Equal("System.Collections.Generic.List", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "2").VerifyDiagnostics(); + + string source2 = @" +using System; +using System.Linq.Expressions; + +public class C +{ + static void Main() + { + var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Print(expr); + } + + System.Collections.Generic.List _test1 = new System.Collections.Generic.List(); + ref System.Collections.Generic.List this[int x] + { + get => ref _test1; + } + + static Expression> GetExpression(Expression> ex) => ex; + static void Print(Expression> expr) + { + System.Console.Write(expr); + } +} +"; + + var comp2 = CreateCompilation(source2, options: TestOptions.DebugExe); + comp2.VerifyDiagnostics( + // (9,59): error CS8074: An expression tree lambda may not contain a dictionary initializer. + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_DictionaryInitializerInExpressionTree, "[d]").WithLocation(9, 59), + // (9,59): error CS8153: An expression tree lambda may not contain a call to a method, property, or indexer that returns by reference + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_RefReturningCallInExpressionTree, "[d]").WithLocation(9, 59), + // (9,60): error CS1963: An expression tree may not contain a dynamic operation + // var expr = GetExpression((dynamic d) => new C() { [d] = {2} }); + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsDynamicOperation, "d").WithLocation(9, 60) + ); + + string source3 = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C() { [d] = {2} }; + } + + C2 _test1 = new C2(); + ref C2 this[int x] + { + get => ref _test1; + } +} + +class C2 +{ +} +"; + + var comp3 = CreateCompilation(source3, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + comp3.VerifyDiagnostics( + // (7,33): error CS1922: Cannot initialize type 'C2' with a collection initializer because it does not implement 'System.Collections.IEnumerable' + // var c = new C() { [d] = {2} }; + Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{2}").WithArguments("C2").WithLocation(7, 33) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((dynamic)2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((dynamic?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Tuple_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (""2"", 123); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (8,30): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var a = (c[d], _) = ("2", 123); + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""2""").WithArguments("string", "int").WithLocation(8, 30) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment_Deconstruction_Tuple() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = (2, 123); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(System.Int32, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 (System.Int32, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal(tupleTypeInfo.Type, operation.Value.Type); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = ((int?)null, 123); + Print(a.Item1); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a.Item1); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a.Item1").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_01() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + comp3.VerifyDiagnostics(); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/33011")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72913")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_02() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + dynamic this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic (dynamic, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("dynamic C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsBoxing: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + // The meaningless warning is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72913 + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int' for parameter 'x' in 'void C2.Deconstruct(out int x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int", "x", "void C2.Deconstruct(out int x, out int y)").WithLocation(10, 18) + ); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + dynamic? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // Nullability is not tracked across deconstruction, this is a pre-existing condition - https://github.com/dotnet/roslyn/issues/33011 + // The meaningless warning is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72913 + comp3.VerifyDiagnostics( + // (10,18): warning CS8624: Argument of type 'dynamic' cannot be used as an output of type 'int?' for parameter 'x' in 'void C2.Deconstruct(out int? x, out int y)' due to differences in the nullability of reference types. + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgumentForOutput, "c[d]").WithArguments("dynamic", "int?", "x", "void C2.Deconstruct(out int? x, out int y)").WithLocation(10, 18) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + [WorkItem("https://github.com/dotnet/roslyn/issues/72914")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_03() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out dynamic x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + // The unexpected error is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72914 + comp.VerifyDiagnostics( + // (10,18): error CS0266: Cannot implicitly convert type 'dynamic' to 'int'. An explicit conversion exists (are you missing a cast?) + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c[d]").WithArguments("dynamic", "int").WithLocation(10, 18) + ); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + int? this[int x] + { + get => _test1; + set => _test1 = value; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out dynamic? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + // The unexpected error is a pre-existing condition - https://github.com/dotnet/roslyn/issues/72914 + comp3.VerifyDiagnostics( + // (10,18): error CS0266: Cannot implicitly convert type 'dynamic' to 'int?'. An explicit conversion exists (are you missing a cast?) + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c[d]").WithArguments("dynamic", "int?").WithLocation(10, 18) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Method_RightSideIsConvertedStatically() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public void Deconstruct(out string x, out int y) + { + (x, y) = (""2"", 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + comp.VerifyDiagnostics( + // (8,18): error CS0029: Cannot implicitly convert type 'string' to 'int' + // var a = (c[d], _) = new C2(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "c[d]").WithArguments("string", "int").WithLocation(8, 18) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_RefReturning_Property_Assignment_Deconstruction_Method() + { + string source = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + System.Console.Write(a); + Print(a.Item1); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + ref int this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").First(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(System.Int32, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("System.Int32 (System.Int32, System.Int32).Item1", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("ref System.Int32 C.this[System.Int32 x] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var propertyRef = (IPropertyReferenceOperation)model.GetOperation(elementAccess); + AssertEx.Equal(symbolInfo.Symbol.ToTestDisplayString(), propertyRef.Property.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, propertyRef.Type); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + var tupleTypeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }); + + var operation = (IDeconstructionAssignmentOperation)model.GetOperation(assignment); + Assert.Equal(tupleTypeInfo.Type, operation.Target.Type); + Assert.Equal("C2", operation.Value.Type.ToTestDisplayString()); + Assert.Equal(typeInfo.Type, operation.Type); + + var right = assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2").VerifyDiagnostics(); + + string source3 = @" +#nullable enable + +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = new C2(); + Print(a.Item1); + } + + int? _test1 = 0; + ref int? this[int x] + { + get => ref _test1; + } + + static void Print(dynamic b) + { + } +} + +class C2 +{ + public void Deconstruct(out int? x, out int y) + { + (x, y) = (2, 123); + } +} +"; + var comp3 = CreateCompilation(source3, targetFramework: TargetFramework.StandardAndCSharp); + + comp3.VerifyDiagnostics( + // (11,15): warning CS8604: Possible null reference argument for parameter 'b' in 'void C.Print(dynamic b)'. + // Print(a.Item1); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "a.Item1").WithArguments("b", "void C.Print(dynamic b)").WithLocation(11, 15) + ); + + tree = comp3.SyntaxTrees.Single(); + node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "Item1").Single(); + model = comp3.GetSemanticModel(tree); + + typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("System.Int32?", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.MaybeNull, typeInfo.Nullability.FlowState); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Nested_01() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ((c[d], _), _) = ((2, 123), 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("((dynamic, System.Int32), System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + left = (TupleExpressionSyntax)left.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }, _] }); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + right = (TupleExpressionSyntax)right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "((2, 123), 124) 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Nested_02() + { + string source = @" +public class C +{ + public static void Main() + { + dynamic d = 1; + var c = new C(); + var a = ((c[d], _), _) = (new C2(), 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} + +class C2 +{ + public void Deconstruct(out int x, out int y) + { + (x, y) = (2, 123); + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("((dynamic, System.Int32), System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + left = (TupleExpressionSyntax)left.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((System.Int32, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("((dynamic, System.Int32), System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + Assert.True(model.GetDeconstructionInfo(assignment) is { Method: null, Conversion: null, Nested: [{ Method: not null, Conversion: null, Nested: [{ Method: null, Conversion: { IsIdentity: true }, Nested: [] }, _] }, _] }); + + var right = (TupleExpressionSyntax)assignment.Right; + typeInfo = model.GetTypeInfo(right); + AssertEx.Equal("(C2, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(C2, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var rightElement = right.Arguments[0].Expression; + typeInfo = model.GetTypeInfo(rightElement); + AssertEx.Equal("C2", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("C2", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "((2, 123), 124) 2").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_Assignment_Deconstruction_Conditional() + { + string source = @" +public class C +{ + public static void Main() + { + Test(true); + System.Console.Write("" ""); + Test(false); + } + + static void Test(bool b) + { + dynamic d = 1; + var c = new C(); + var a = (c[d], _) = b ? (2, 123) : (3, 124); + System.Console.Write(a); + System.Console.Write("" ""); + System.Console.Write(c._test1); + } + + int _test1 = 0; + int this[int x] + { + get => _test1; + set => _test1 = value; + } +} +"; + + var comp = CreateCompilation(source, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "a").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("(dynamic, System.Int32) a", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.Int32 x] { get; set; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("System.Int32", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("System.Int32", typeInfo.ConvertedType.ToTestDisplayString()); + + var left = (TupleExpressionSyntax)elementAccess.Parent.Parent; + typeInfo = model.GetTypeInfo(left); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(System.Int32, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + var assignment = (AssignmentExpressionSyntax)left.Parent; + typeInfo = model.GetTypeInfo(assignment); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("(dynamic, System.Int32)", typeInfo.ConvertedType.ToTestDisplayString()); + + CompileAndVerify(comp, expectedOutput: "(2, 123) 2 (3, 124) 3").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_CSharp12_01() + { + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_CSharp12_02() + { + string source1 = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1.Test(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int Test(string name, object value); +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp1 = CreateCompilation(source1, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1.Test(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Int32 I1.Test(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_Extension_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C().Test(""name"", d); + System.Console.Write(result); + } +} + +static class Extensions +{ + public static int Test(this C c, string name, object value) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var model = comp1.GetSemanticModel(tree); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Equal(OperationKind.Invalid, model.GetOperation(call).Kind); + + comp1.VerifyDiagnostics( + // (7,22): error CS1973: 'C' has no applicable method named 'Test' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax. + // var result = new C().Test("name", d); + Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, @"new C().Test(""name"", d)").WithArguments("C", "Test").WithLocation(7, 22) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static int Test(string name, object value, params int[] list) => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.Test(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Delegate_CSharp12() + { + string source = @" +public class C +{ + static C M(Test i1, dynamic value) + { + var result = i1(""name"", value); + return JsonSerializer.Deserialize(result); + } +} + +delegate object Test(string name, object value); + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T); +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"i1(""name"", value)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal("System.Object Test.Invoke(System.String name, System.Object value)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Delegate_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = Test(""name"", d); + System.Console.Write(result); + } + + static D Test = (string name, object value, params int[] list) => 123; + delegate int D(string name, object value, params int[] list); +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var call = tree.GetRoot().DescendantNodes().OfType().First(); + AssertEx.Equal(@"Test(""name"", d)", call.ToString()); + symbolInfo = model.GetSymbolInfo(call); + AssertEx.Equal(@"System.Int32 C.D.Invoke(System.String name, System.Object value, params System.Int32[] list)", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(call); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicInvocationOperation)model.GetOperation(call); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CSharp12_01() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + object this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Object I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_Property_CSharp12_02() + { + string source = @" +#nullable enable + +public class C +{ + public static C M(I1 i1, dynamic value) + { + var result = i1[""name"", value]; + return JsonSerializer.Deserialize(result); + } +} + +public interface I1 +{ + int this[string name, object value] {get;} +} + +class JsonSerializer +{ + public static T Deserialize(System.IO.Stream c) => default(T)!; +} +"; + + var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic? result", symbolInfo.Symbol.ToTestDisplayString()); + + var typeInfo = model.GetTypeInfo(node); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + Assert.Equal(CodeAnalysis.NullableFlowState.NotNull, typeInfo.Nullability.FlowState); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 I1.this[System.String name, System.Object value] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp).VerifyDiagnostics(); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/72750")] + public void SingleCandidate_ResultIsDynamic_ParamArray_Property_CSharp12() + { + string source1 = @" +public class C +{ + static void Main() + { + dynamic d = 1; + var result = new C()[""name"", d]; + System.Console.Write(result); + } + + int this[string name, object value, params int[] list] => 123; +} +"; + + var comp1 = CreateCompilation(source1, options: TestOptions.DebugExe, targetFramework: TargetFramework.StandardAndCSharp, parseOptions: TestOptions.Regular12); + + // This is surprising, but this scenario used to successfully bind dynamically before (unlike a call). + var tree = comp1.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "result").Single(); + var model = comp1.GetSemanticModel(tree); + var symbolInfo = model.GetSymbolInfo(node); + Assert.Equal("dynamic result", symbolInfo.Symbol.ToTestDisplayString()); + + var elementAccess = tree.GetRoot().DescendantNodes().OfType().Single(); + symbolInfo = model.GetSymbolInfo(elementAccess); + AssertEx.Equal("System.Int32 C.this[System.String name, System.Object value, params System.Int32[] list] { get; }", symbolInfo.Symbol.ToTestDisplayString()); + var typeInfo = model.GetTypeInfo(elementAccess); + AssertEx.Equal("dynamic", typeInfo.Type.ToTestDisplayString()); + AssertEx.Equal("dynamic", typeInfo.ConvertedType.ToTestDisplayString()); + + var operation = (IDynamicIndexerAccessOperation)model.GetOperation(elementAccess); + AssertEx.Equal("dynamic", operation.Type.ToTestDisplayString()); + + CompileAndVerify(comp1, expectedOutput: "123").VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs index b31d4f8291d41..b4990d8031bfd 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs @@ -3192,7 +3192,7 @@ static void M() TestOperatorKinds(code); } - private void TestBoundTree(string source, System.Func>, IEnumerable> query) + private static void TestBoundTree(string source, System.Func>, IEnumerable> query) { // The mechanism of this test is: we build the bound tree for the code passed in and then extract // from it the nodes that describe the operators. We then compare the description of @@ -3217,7 +3217,7 @@ private void TestBoundTree(string source, System.Func from edge in edges @@ -3260,7 +3260,7 @@ select string.Join(" ", from child in node.Children }))); } - private void TestTypes(string source) + internal static void TestTypes(string source) { TestBoundTree(source, edges => from edge in edges @@ -3289,7 +3289,7 @@ private static string FormatTypeArgumentList(ImmutableArray return s + ">"; } - private void TestDynamicMemberAccessCore(string source) + internal static void TestDynamicMemberAccessCore(string source) { TestBoundTree(source, edges => from edge in edges diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs index 882913c850da3..2e233219a3fc0 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetSemanticInfoTests_LateBound.cs @@ -225,7 +225,7 @@ public void M(dynamic d) "; var semanticInfo = GetSemanticInfoForTest(sourceCode1); - Assert.Equal("C", semanticInfo.Type.ToTestDisplayString()); + Assert.True(semanticInfo.Type.IsDynamic()); Assert.Equal("C C.Create(System.Int32 arg)", semanticInfo.Symbol.ToTestDisplayString()); Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); Assert.Equal(0, semanticInfo.CandidateSymbols.Length); @@ -548,8 +548,8 @@ public int this[int a] "; var semanticInfo = GetSemanticInfoForTest(sourceCode); - Assert.False(semanticInfo.Type.IsDynamic()); - Assert.False(semanticInfo.ConvertedType.IsDynamic()); + Assert.True(semanticInfo.Type.IsDynamic()); + Assert.True(semanticInfo.ConvertedType.IsDynamic()); Assert.Equal(ConversionKind.Identity, semanticInfo.ImplicitConversion.Kind); Assert.Equal(CandidateReason.None, semanticInfo.CandidateReason); From aa8aab40f7b376ef5b7e379eac8d9657ea2c5208 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 17 Apr 2024 09:25:00 +0200 Subject: [PATCH 046/292] Replace deprecated pool image (#73043) --- azure-pipelines-official.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index bd33388a7258b..ec131688b0f32 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -85,7 +85,7 @@ extends: sdl: sourceAnalysisPool: name: NetCore1ESPool-Svc-Internal - image: 1es-windows-2022-pt + image: 1es-windows-2022 os: windows sbom: enabled: false From 82fbe32d58a469c90f3973bd5f5c23d738fb7a0e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 12:02:12 -0700 Subject: [PATCH 047/292] Pass along cancellation token in snippets --- .../AbstractSnippetFunction.cs | 19 ++++++------------- .../SnippetFunctionClassName.cs | 11 ++--------- .../SnippetFunctionGenerateSwitchCases.cs | 7 ++----- .../SnippetFunctionSimpleTypeName.cs | 9 ++------- 4 files changed, 12 insertions(+), 34 deletions(-) diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs index 61aba67eadf6e..d7145bc6db4ea 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/AbstractSnippetFunction.cs @@ -27,18 +27,13 @@ public AbstractSnippetFunction(SnippetExpansionClient snippetExpansionClient, IT _threadingContext = threadingContext; } - protected bool TryGetDocument([NotNullWhen(true)] out Document? document) - { - document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges()?.WithFrozenPartialSemantics(CancellationToken.None); - return document != null; - } + protected Document? GetDocument(CancellationToken cancellationToken) + => _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges()?.WithFrozenPartialSemantics(cancellationToken); private int GetDefaultValue(CancellationToken cancellationToken, out string value, out int hasDefaultValue) { - var (ExitCode, Value, HasDefaultValue) = _threadingContext.JoinableTaskFactory.Run(() => GetDefaultValueAsync(cancellationToken)); - value = Value; - hasDefaultValue = HasDefaultValue; - return ExitCode; + (var exitCode, value, hasDefaultValue) = _threadingContext.JoinableTaskFactory.Run(() => GetDefaultValueAsync(cancellationToken)); + return exitCode; } protected virtual Task<(int ExitCode, string Value, int HasDefaultValue)> GetDefaultValueAsync(CancellationToken cancellationToken) @@ -48,10 +43,8 @@ private int GetDefaultValue(CancellationToken cancellationToken, out string valu private int GetCurrentValue(CancellationToken cancellationToken, out string value, out int hasCurrentValue) { - var (ExitCode, Value, HasCurrentValue) = _threadingContext.JoinableTaskFactory.Run(() => GetCurrentValueAsync(cancellationToken)); - value = Value; - hasCurrentValue = HasCurrentValue; - return ExitCode; + (var exitCode, value, hasCurrentValue) = _threadingContext.JoinableTaskFactory.Run(() => GetCurrentValueAsync(cancellationToken)); + return exitCode; } protected virtual Task<(int ExitCode, string Value, int HasCurrentValue)> GetCurrentValueAsync(CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs index a732df3a371ef..015dd6d99bc9e 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionClassName.cs @@ -28,29 +28,22 @@ public SnippetFunctionClassName(SnippetExpansionClient snippetExpansionClient, I { var hasDefaultValue = 0; var value = string.Empty; - if (!TryGetDocument(out var document)) - { + var document = GetDocument(cancellationToken); + if (document is null) return (VSConstants.E_FAIL, value, hasDefaultValue); - } var surfaceBufferFieldSpan = new VsTextSpan[1]; if (snippetExpansionClient.ExpansionSession.GetFieldSpan(FieldName, surfaceBufferFieldSpan) != VSConstants.S_OK) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } if (!snippetExpansionClient.TryGetSubjectBufferSpan(surfaceBufferFieldSpan[0], out var subjectBufferFieldSpan)) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } var snippetFunctionService = document.Project.GetRequiredLanguageService(); value = await snippetFunctionService.GetContainingClassNameAsync(document, subjectBufferFieldSpan.Start.Position, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(value)) - { hasDefaultValue = 1; - } return (VSConstants.S_OK, value, hasDefaultValue); } diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs index 6044dce6fe387..526e0a7091825 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionGenerateSwitchCases.cs @@ -41,10 +41,9 @@ protected override int FieldChanged(string field, out int requeryFunction) protected override async Task<(int ExitCode, string Value, int HasCurrentValue)> GetCurrentValueAsync(CancellationToken cancellationToken) { - if (!TryGetDocument(out var document)) - { + var document = GetDocument(cancellationToken); + if (document is null) return (VSConstants.S_OK, string.Empty, HasCurrentValue: 0); - } // If the switch expression is invalid, still show the default case var hasCurrentValue = 1; @@ -60,9 +59,7 @@ protected override int FieldChanged(string field, out int requeryFunction) var value = await snippetFunctionService.GetSwitchExpansionAsync(document, caseGenerationSpan.Value, switchExpressionSpan.Value, simplifierOptions, cancellationToken).ConfigureAwait(false); if (value == null) - { return (VSConstants.S_OK, snippetFunctionService.SwitchDefaultCaseForm, hasCurrentValue); - } return (VSConstants.S_OK, value, hasCurrentValue); } diff --git a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs index 81ce994883a1e..77665d5efd398 100644 --- a/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs +++ b/src/VisualStudio/Core/Def/Snippets/SnippetFunctions/SnippetFunctionSimpleTypeName.cs @@ -35,23 +35,18 @@ public SnippetFunctionSimpleTypeName( { var value = _fullyQualifiedName; var hasDefaultValue = 1; - if (!TryGetDocument(out var document)) - { + var document = GetDocument(cancellationToken); + if (document is null) return (VSConstants.E_FAIL, value, hasDefaultValue); - } if (!TryGetFieldSpan(out var fieldSpan)) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } var simplifierOptions = await document.GetSimplifierOptionsAsync(snippetExpansionClient.EditorOptionsService.GlobalOptions, cancellationToken).ConfigureAwait(false); var simplifiedTypeName = await SnippetFunctionService.GetSimplifiedTypeNameAsync(document, fieldSpan.Value, _fullyQualifiedName, simplifierOptions, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(simplifiedTypeName)) - { return (VSConstants.E_FAIL, value, hasDefaultValue); - } return (VSConstants.S_OK, simplifiedTypeName!, hasDefaultValue); } From e2059ce9a3566bae41d6d56d8ccc4b084e9e6702 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 12:33:33 -0700 Subject: [PATCH 048/292] Add initial caching logic --- .../Microsoft.CodeAnalysis.Workspaces.csproj | 4 +- .../DefaultDocumentTrackingService.cs | 13 +--- .../IDocumentTrackingService.cs | 0 .../IDocumentTrackingServiceExtensions.cs | 0 .../Portable/Workspace/Solution/Document.cs | 74 +++++++++---------- .../Workspace_SemanticModelCaching.cs | 50 +++++++++++++ 6 files changed, 91 insertions(+), 50 deletions(-) rename src/{Features/Core/Portable/SolutionCrawler => Workspaces/Core/Portable/Workspace/DocumentTracking}/DefaultDocumentTrackingService.cs (75%) rename src/{Features/Core/Portable/SolutionCrawler => Workspaces/Core/Portable/Workspace/DocumentTracking}/IDocumentTrackingService.cs (100%) rename src/{Features/Core/Portable/SolutionCrawler => Workspaces/Core/Portable/Workspace/DocumentTracking}/IDocumentTrackingServiceExtensions.cs (100%) create mode 100644 src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 3c1b1a4561ae0..2ef8b99744fab 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -105,7 +105,7 @@ - + @@ -131,7 +131,7 @@ - + diff --git a/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs similarity index 75% rename from src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs rename to src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs index 75022a16733cf..c070a4f5ea9a1 100644 --- a/src/Features/Core/Portable/SolutionCrawler/DefaultDocumentTrackingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs @@ -9,16 +9,11 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler; -[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Default)] -[Shared] -internal sealed class DefaultDocumentTrackingService : IDocumentTrackingService +[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Default), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DefaultDocumentTrackingService() : IDocumentTrackingService { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultDocumentTrackingService() - { - } - public bool SupportsDocumentTracking => false; public event EventHandler ActiveDocumentChanged { add { } remove { } } diff --git a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs similarity index 100% rename from src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingService.cs rename to src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs diff --git a/src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingServiceExtensions.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs similarity index 100% rename from src/Features/Core/Portable/SolutionCrawler/IDocumentTrackingServiceExtensions.cs rename to src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 7f2bffd0dd681..8f96644bb5ca3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -326,60 +326,56 @@ internal async ValueTask GetRequiredNullableDisabledSemanticModel /// private async Task GetSemanticModelHelperAsync(bool disableNullableAnalysis, CancellationToken cancellationToken) { - try - { - if (!this.SupportsSemanticModel) - { - return null; - } + if (!this.SupportsSemanticModel) + return null; - SemanticModel? semanticModel; - if (disableNullableAnalysis) + var semanticModel = await GetSemanticModelWorkerAsync().ConfigureAwait(false); + this.Project.Solution.Workspace.OnSemanticModelObtained(this.Id, semanticModel); + return semanticModel; + + async Task GetSemanticModelWorkerAsync() + { + try { - if (this.TryGetNullableDisabledSemanticModel(out semanticModel)) + if (disableNullableAnalysis) { - return semanticModel; + if (this.TryGetNullableDisabledSemanticModel(out var semanticModel)) + return semanticModel; } - } - else - { - if (this.TryGetSemanticModel(out semanticModel)) + else { - return semanticModel; + if (this.TryGetSemanticModel(out var semanticModel)) + return semanticModel; } - } - var syntaxTree = await this.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var compilation = await this.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var syntaxTree = await this.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var compilation = await this.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); #pragma warning disable RSEXPERIMENTAL001 // sym-shipped usage of experimental API - var result = compilation.GetSemanticModel(syntaxTree, disableNullableAnalysis ? SemanticModelOptions.DisableNullableAnalysis : SemanticModelOptions.None); + var result = compilation.GetSemanticModel(syntaxTree, disableNullableAnalysis ? SemanticModelOptions.DisableNullableAnalysis : SemanticModelOptions.None); #pragma warning restore RSEXPERIMENTAL001 - Contract.ThrowIfNull(result); - var original = Interlocked.CompareExchange(ref disableNullableAnalysis ? ref _nullableDisabledModel : ref _model, new WeakReference(result), null); + Contract.ThrowIfNull(result); + var original = Interlocked.CompareExchange(ref disableNullableAnalysis ? ref _nullableDisabledModel : ref _model, new WeakReference(result), null); - // okay, it is first time. - if (original == null) - { - return result; - } + // okay, it is first time. + if (original == null) + return result; - // It looks like someone has set it. Try to reuse same semantic model, or assign the new model if that - // fails. The lock is required since there is no compare-and-set primitive for WeakReference. - lock (original) - { - if (original.TryGetTarget(out semanticModel)) + // It looks like someone has set it. Try to reuse same semantic model, or assign the new model if that + // fails. The lock is required since there is no compare-and-set primitive for WeakReference. + lock (original) { - return semanticModel; - } + if (original.TryGetTarget(out var semanticModel)) + return semanticModel; - original.SetTarget(result); - return result; + original.SetTarget(result); + return result; + } + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) + { + throw ExceptionUtilities.Unreachable(); } - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) - { - throw ExceptionUtilities.Unreachable(); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs b/src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs new file mode 100644 index 0000000000000..95f2d1e1372a8 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis; + +internal partial class Workspace +{ + /// + /// Strongly held reference to the semantic model for the active document. By strongly holding onto it, we ensure + /// that it won't be GC'ed between feature requests from multiple features that care about it. As the active + /// document has the most features running on it continuously, we definitely do not want to drop this. Note: this + /// cached value is only to help with performance. Not with correctness. Importantly, the concept of 'active + /// document' is itself fundamentally racy. That's ok though as we simply want to settle on these semantic models + /// settling into a stable state over time. We don't need to be perfect about it. They are intentionally not + /// locked either as we would only have contention right when switching to a new active document, and we would still + /// latch onto the new document very quickly. + /// + private SemanticModel? _activeDocumentSemanticModel; + + /// + private SemanticModel? _activeDocumentNullableDisabledSemanticModel; + + internal void OnSemanticModelObtained(DocumentId documentId, SemanticModel semanticModel) + { + var service = this.Services.GetRequiredService(); + if (!service.SupportsDocumentTracking) + return; + + var activeDocumentId = service.TryGetActiveDocument(); + if (activeDocumentId != documentId) + return; + + // Ok. We just obtained the semantic model for the active document. Make a strong reference to it so that + // other features that wake up for this active document are sure to be able to reuse the same one. +#pragma warning disable RSEXPERIMENTAL001 // sym-shipped usage of experimental API + if (semanticModel.NullableAnalysisIsDisabled) + _activeDocumentNullableDisabledSemanticModel = semanticModel; + else + _activeDocumentSemanticModel = semanticModel; +#pragma warning restore RSEXPERIMENTAL001 + } +} From a84d7a15b6f67537a68c3647f23eb4c8253956f2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 12:47:25 -0700 Subject: [PATCH 049/292] Add remote api --- .../Core/Remote/SolutionChecksumUpdater.cs | 38 +++++++++++++++++++ .../IDocumentTrackingServiceExtensions.cs | 3 -- .../IRemoteAssetSynchronizationService.cs | 20 +++++++++- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 92187c42e3748..d0c707add134e 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -28,6 +28,8 @@ internal sealed class SolutionChecksumUpdater /// private readonly IGlobalOperationNotificationService? _globalOperationService; + private readonly IDocumentTrackingService _documentTrackingService; + /// /// Queue to push out text changes in a batched fashion when we hear about them. Because these should be short /// operations (only syncing text changes) we don't cancel this when we enter the paused state. We simply don't @@ -40,6 +42,11 @@ internal sealed class SolutionChecksumUpdater /// private readonly AsyncBatchingWorkQueue _synchronizeWorkspaceQueue; + /// + /// Queue for kicking off the work to synchronize the active document to the remote process. + /// + private readonly AsyncBatchingWorkQueue _synchronizeActiveDocumentQueue; + private readonly object _gate = new(); private bool _isPaused; @@ -53,6 +60,7 @@ public SolutionChecksumUpdater( _globalOperationService = workspace.Services.SolutionServices.ExportProvider.GetExports().FirstOrDefault()?.Value; _workspace = workspace; + _documentTrackingService = workspace.Services.GetRequiredService(); _textChangeQueue = new AsyncBatchingWorkQueue<(Document? oldDocument, Document? newDocument)>( DelayTimeSpan.NearImmediate, @@ -66,9 +74,17 @@ public SolutionChecksumUpdater( listener, shutdownToken); + _synchronizeActiveDocumentQueue = new AsyncBatchingWorkQueue( + DelayTimeSpan.NearImmediate, + SynchronizeActiveDocumentAsync, + listener, + shutdownToken); + // start listening workspace change event _workspace.WorkspaceChanged += OnWorkspaceChanged; + _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; + if (_globalOperationService != null) { _globalOperationService.Started += OnGlobalOperationStarted; @@ -84,6 +100,9 @@ public void Shutdown() // Try to stop any work that is in progress. PauseWork(); + var trackingService = _workspace.Services.GetRequiredService(); + trackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; + _workspace.WorkspaceChanged -= OnWorkspaceChanged; if (_globalOperationService != null) @@ -106,6 +125,7 @@ private void PauseWork() lock (_gate) { _synchronizeWorkspaceQueue.CancelExistingWork(); + _synchronizeActiveDocumentQueue.CancelExistingWork(); _isPaused = true; } } @@ -115,6 +135,7 @@ private void ResumeWork() lock (_gate) { _isPaused = false; + _synchronizeActiveDocumentQueue.AddWork(); _synchronizeWorkspaceQueue.AddWork(); } } @@ -137,6 +158,9 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) _synchronizeWorkspaceQueue.AddWork(); } + private void OnActiveDocumentChanged(object? sender, DocumentId? e) + => _synchronizeActiveDocumentQueue.AddWork(); + private async ValueTask SynchronizePrimaryWorkspaceAsync(CancellationToken cancellationToken) { var solution = _workspace.CurrentSolution; @@ -153,6 +177,20 @@ await client.TryInvokeAsync( } } + private async ValueTask SynchronizeActiveDocumentAsync(CancellationToken cancellationToken) + { + var activeDocument = _documentTrackingService.TryGetActiveDocument(); + + var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false); + if (client == null) + return; + + var solution = _workspace.CurrentSolution; + await client.TryInvokeAsync( + (service, cancellationToken) => service.SynchronizeActiveDocumentAsync(activeDocument), + cancellationToken).ConfigureAwait(false); + } + private async ValueTask SynchronizeTextChangesAsync( ImmutableSegmentedList<(Document? oldDocument, Document? newDocument)> values, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs index 6d3b0cff1a372..9a231ee40abbc 100644 --- a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingServiceExtensions.cs @@ -2,11 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; diff --git a/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs index 455c83c4177ed..5982dfe6dd27d 100644 --- a/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/Core/IRemoteAssetSynchronizationService.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; @@ -16,5 +17,22 @@ internal interface IRemoteAssetSynchronizationService /// call into it. /// ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken); - ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, IEnumerable textChanges, CancellationToken cancellationToken); + + /// + /// Synchronize the text changes made by a user to a particular document as they are editing it. By sending over + /// the text changes as they happen, we can attempt to 'prime' the remote asset cache with a final that is built based off of retrieving the remote source text with a checksum corresponding + /// to and then applying the to it. Then, when + /// the next remote call comes in for the new solution snapshot, it can hopefully just pluck that text out of the + /// cache without having to sync the entire contents of the file over. + /// + ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, ImmutableArray textChanges, CancellationToken cancellationToken); + + /// + /// Synchronize over what the user's current active document is that they're editing. This can then be used by the + /// remote side to help determine which documents are best to strongly hold onto data for, and which should just + /// hold on weakly. Given how much work happens on the active document, this can help avoid the remote side from + /// continually creating and then throwing away that data. + /// + ValueTask SynchronizeActiveDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken); } From 9921a0181e42c7593e2702e79e85fd610bbe3fe3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 12:49:59 -0700 Subject: [PATCH 050/292] Move where cache is --- .../Core/Portable/Workspace/Solution/Document.cs | 2 +- .../Solution_SemanticModelCaching.cs} | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) rename src/Workspaces/Core/Portable/Workspace/{Workspace_SemanticModelCaching.cs => Solution/Solution_SemanticModelCaching.cs} (85%) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 8f96644bb5ca3..379fabf46b317 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -330,7 +330,7 @@ internal async ValueTask GetRequiredNullableDisabledSemanticModel return null; var semanticModel = await GetSemanticModelWorkerAsync().ConfigureAwait(false); - this.Project.Solution.Workspace.OnSemanticModelObtained(this.Id, semanticModel); + this.Project.Solution.OnSemanticModelObtained(this.Id, semanticModel); return semanticModel; async Task GetSemanticModelWorkerAsync() diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs similarity index 85% rename from src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs index 95f2d1e1372a8..3d69d50db5287 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace_SemanticModelCaching.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis; -internal partial class Workspace +internal partial class Solution { /// /// Strongly held reference to the semantic model for the active document. By strongly holding onto it, we ensure @@ -23,10 +23,16 @@ internal partial class Workspace /// locked either as we would only have contention right when switching to a new active document, and we would still /// latch onto the new document very quickly. /// + /// + /// It is fine for these fields to never be read. The purpose is simply to keep a strong reference around so that + /// they will not be GC'ed as long as the active document stays the same. + /// +#pragma warning disable IDE0052 // Remove unread private members private SemanticModel? _activeDocumentSemanticModel; /// private SemanticModel? _activeDocumentNullableDisabledSemanticModel; +#pragma warning restore IDE0052 // Remove unread private members internal void OnSemanticModelObtained(DocumentId documentId, SemanticModel semanticModel) { From 0bc3830afcf5fea6f1dd7607ecfe858bbcb69efa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 12:56:37 -0700 Subject: [PATCH 051/292] Simplify and docs --- .../DefaultDocumentTrackingService.cs | 1 - .../IDocumentTrackingService.cs | 27 ++++++--- .../ServiceHubDocumentTrackingService.cs | 60 ++++++++----------- 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs index c070a4f5ea9a1..3832ac9faed5b 100644 --- a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs @@ -17,7 +17,6 @@ internal sealed class DefaultDocumentTrackingService() : IDocumentTrackingServic public bool SupportsDocumentTracking => false; public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } public ImmutableArray GetVisibleDocuments() => []; diff --git a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs index 2e80613840883..6944f40fade72 100644 --- a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/IDocumentTrackingService.cs @@ -8,25 +8,34 @@ namespace Microsoft.CodeAnalysis; +/// +/// Retrieves information about what documents are currently active or visible in the host workspace. Note: this +/// information is fundamentally racy (it can change directly after it is requested), and on different threads than the +/// thread that asks for it. As such, this information must only be used to provide a hint towards how a +/// feature should go about its work, it must not impact the final results that a feature produces. For example, a +/// feature is allowed to use this information to decide what order to process documents in, to try to get more relevant +/// results to a client more quickly. However, it is not allowed to use this information to decide what results to +/// return altogether. Hosts are free to implement this service to do nothing at all, always returning empty/default +/// values for the members within. As per the above, this should never affect correctness, but it may impede a +/// feature's ability to provide results in as timely a manner as possible for a client. +/// internal interface IDocumentTrackingService : IWorkspaceService { - bool SupportsDocumentTracking { get; } - /// - /// Get the of the active document. May be null if there is no active document - /// or the active document is not in the workspace. + /// Get the of the active document. May be null if there is no active document, the + /// active document is not in the workspace, or if this functionality is not supported by a particular host. /// DocumentId? TryGetActiveDocument(); /// - /// Get a read only collection of the s of all the visible documents in the workspace. + /// Get a read only collection of the s of all the visible documents in the workspace. May + /// be empty if there are no visible documents, or if this functionality is not supported by a particular host. /// ImmutableArray GetVisibleDocuments(); - event EventHandler ActiveDocumentChanged; - /// - /// Raised when a text buffer that's not part of a workspace is changed. + /// Fired when the active document changes. A host is not required to support this event, even if it implements + /// . /// - event EventHandler NonRoslynBufferTextChanged; + event EventHandler ActiveDocumentChanged; } diff --git a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs index d3cbbf091565b..1a7d27770edc5 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs @@ -9,42 +9,32 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; -namespace Microsoft.CodeAnalysis.Remote +namespace Microsoft.CodeAnalysis.Remote; + +[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Host), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class RemoteDocumentTrackingService() : IDocumentTrackingService { - [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Host)] - [Shared] - internal sealed class ServiceHubDocumentTrackingService : IDocumentTrackingService + public bool SupportsDocumentTracking => true; + + public event EventHandler ActiveDocumentChanged { add { } remove { } } + + public ImmutableArray GetVisibleDocuments() + => []; + + public DocumentId? TryGetActiveDocument() { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public ServiceHubDocumentTrackingService() - { - } - - public bool SupportsDocumentTracking => false; - - public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } - - public ImmutableArray GetVisibleDocuments() - { - Fail("Code should not be attempting to obtain visible documents from a stateless remote invocation."); - return []; - } - - public DocumentId? TryGetActiveDocument() - { - Fail("Code should not be attempting to obtain active document from a stateless remote invocation."); - return null; - } - - private static void Fail(string message) - { - // assert in debug builds to hopefully catch problems in CI - Debug.Fail(message); - - // record NFW to see who violates contract. - FatalError.ReportAndCatch(new InvalidOperationException(message)); - } + Fail("Code should not be attempting to obtain active document from a stateless remote invocation."); + return null; + } + + private static void Fail(string message) + { + // assert in debug builds to hopefully catch problems in CI + Debug.Fail(message); + + // record NFW to see who violates contract. + FatalError.ReportAndCatch(new InvalidOperationException(message)); } } From 9c5cc52365e72aca6a6d327d9564463510694f29 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 13:40:16 -0700 Subject: [PATCH 052/292] Add support for remoting the active file --- .../Services/SolutionServiceTests.cs | 1399 ++++++++--------- .../RemoteAssetSynchronizationService.cs | 172 +- .../ServiceHubDocumentTrackingService.cs | 20 +- 3 files changed, 794 insertions(+), 797 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 287ddfe63a30b..2e7750b7f31ed 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -27,898 +27,897 @@ using Roslyn.Utilities; using Xunit; -namespace Roslyn.VisualStudio.Next.UnitTests.Remote +namespace Roslyn.VisualStudio.Next.UnitTests.Remote; + +[UseExportProvider] +[Trait(Traits.Feature, Traits.Features.RemoteHost)] +public class SolutionServiceTests { - [UseExportProvider] - [Trait(Traits.Feature, Traits.Features.RemoteHost)] - public class SolutionServiceTests + private static RemoteWorkspace CreateRemoteWorkspace() + => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); + + [Fact] + public async Task TestCreation() { - private static RemoteWorkspace CreateRemoteWorkspace() - => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); + var code = @"class Test { void Method() { } }"; - [Fact] - public async Task TestCreation() - { - var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + var solution = workspace.CurrentSolution; + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solution = workspace.CurrentSolution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + } - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - } + [Theory] + [CombinatorialData] + public async Task TestGetSolutionWithPrimaryFlag(bool updatePrimaryBranch) + { + var code1 = @"class Test1 { void Method() { } }"; - [Theory] - [CombinatorialData] - public async Task TestGetSolutionWithPrimaryFlag(bool updatePrimaryBranch) - { - var code1 = @"class Test1 { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code1); + using var remoteWorkspace = CreateRemoteWorkspace(); - using var workspace = TestWorkspace.CreateCSharp(code1); - using var remoteWorkspace = CreateRemoteWorkspace(); + var solution = workspace.CurrentSolution; + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solution = workspace.CurrentSolution; - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, cancellationToken: CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, cancellationToken: CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(WorkspaceKind.RemoteWorkspace, synched.WorkspaceKind); + } - Assert.Equal(WorkspaceKind.RemoteWorkspace, synched.WorkspaceKind); - } + [Fact] + public async Task TestStrongNameProvider() + { + using var workspace = new AdhocWorkspace(); + using var remoteWorkspace = CreateRemoteWorkspace(); - [Fact] - public async Task TestStrongNameProvider() - { - using var workspace = new AdhocWorkspace(); - using var remoteWorkspace = CreateRemoteWorkspace(); + var filePath = typeof(SolutionServiceTests).Assembly.Location; - var filePath = typeof(SolutionServiceTests).Assembly.Location; + workspace.AddProject( + ProjectInfo.Create( + ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, + filePath: filePath, outputFilePath: filePath)); - workspace.AddProject( - ProjectInfo.Create( - ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, - filePath: filePath, outputFilePath: filePath)); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); + var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); + var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); - var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + var compilationOptions = solution.Projects.First().CompilationOptions; - var compilationOptions = solution.Projects.First().CompilationOptions; + Assert.IsType(compilationOptions.StrongNameProvider); + Assert.IsType(compilationOptions.XmlReferenceResolver); - Assert.IsType(compilationOptions.StrongNameProvider); - Assert.IsType(compilationOptions.XmlReferenceResolver); + var dirName = PathUtilities.GetDirectoryName(filePath); + var array = new[] { dirName, dirName }; + Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); + Assert.Equal(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory, dirName); + } - var dirName = PathUtilities.GetDirectoryName(filePath); - var array = new[] { dirName, dirName }; - Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); - Assert.Equal(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory, dirName); - } + [Fact] + public async Task TestStrongNameProviderEmpty() + { + using var workspace = new AdhocWorkspace(); + using var remoteWorkspace = CreateRemoteWorkspace(); - [Fact] - public async Task TestStrongNameProviderEmpty() - { - using var workspace = new AdhocWorkspace(); - using var remoteWorkspace = CreateRemoteWorkspace(); + var filePath = "testLocation"; - var filePath = "testLocation"; + workspace.AddProject( + ProjectInfo.Create( + ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, + filePath: filePath, outputFilePath: filePath)); - workspace.AddProject( - ProjectInfo.Create( - ProjectId.CreateNewId(), VersionStamp.Create(), "test", "test.dll", LanguageNames.CSharp, - filePath: filePath, outputFilePath: filePath)); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); + var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); + var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); - var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + var compilationOptions = solution.Projects.First().CompilationOptions; - var compilationOptions = solution.Projects.First().CompilationOptions; + Assert.True(compilationOptions.StrongNameProvider is DesktopStrongNameProvider); + Assert.True(compilationOptions.XmlReferenceResolver is XmlFileResolver); - Assert.True(compilationOptions.StrongNameProvider is DesktopStrongNameProvider); - Assert.True(compilationOptions.XmlReferenceResolver is XmlFileResolver); + var array = new string[] { }; + Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); + Assert.Null(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory); + } - var array = new string[] { }; - Assert.Equal(Hash.CombineValues(array, StringComparer.Ordinal), compilationOptions.StrongNameProvider.GetHashCode()); - Assert.Null(((XmlFileResolver)compilationOptions.XmlReferenceResolver).BaseDirectory); - } + [Fact] + public async Task TestCache() + { + var code = @"class Test { void Method() { } }"; - [Fact] - public async Task TestCache() - { - var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + var solution = workspace.CurrentSolution; + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var solution = workspace.CurrentSolution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var first = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + var second = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + + // same instance from cache + Assert.True(object.ReferenceEquals(first, second)); + Assert.Equal(WorkspaceKind.RemoteWorkspace, first.WorkspaceKind); + } - var first = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - var second = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + [Fact] + public async Task TestUpdatePrimaryWorkspace() + { + var code = @"class Test { void Method() { } }"; - // same instance from cache - Assert.True(object.ReferenceEquals(first, second)); - Assert.Equal(WorkspaceKind.RemoteWorkspace, first.WorkspaceKind); - } + await VerifySolutionUpdate(code, s => s.WithDocumentText(s.Projects.First().DocumentIds.First(), SourceText.From(code + " "))); + } - [Fact] - public async Task TestUpdatePrimaryWorkspace() - { - var code = @"class Test { void Method() { } }"; + [Fact] + public async Task ProjectProperties() + { + using var workspace = TestWorkspace.CreateCSharp(""); - await VerifySolutionUpdate(code, s => s.WithDocumentText(s.Projects.First().DocumentIds.First(), SourceText.From(code + " "))); + static Solution SetProjectProperties(Solution solution, int version) + { + var projectId = solution.ProjectIds.Single(); + return solution + .WithProjectName(projectId, "Name" + version) + .WithProjectAssemblyName(projectId, "AssemblyName" + version) + .WithProjectFilePath(projectId, "FilePath" + version) + .WithProjectOutputFilePath(projectId, "OutputFilePath" + version) + .WithProjectOutputRefFilePath(projectId, "OutputRefFilePath" + version) + .WithProjectCompilationOutputInfo(projectId, new CompilationOutputInfo("AssemblyPath" + version)) + .WithProjectDefaultNamespace(projectId, "DefaultNamespace" + version) + .WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1 + version) + .WithHasAllInformation(projectId, (version % 2) != 0) + .WithRunAnalyzers(projectId, (version % 2) != 0); } - [Fact] - public async Task ProjectProperties() + static void ValidateProperties(Solution solution, int version) { - using var workspace = TestWorkspace.CreateCSharp(""); - - static Solution SetProjectProperties(Solution solution, int version) - { - var projectId = solution.ProjectIds.Single(); - return solution - .WithProjectName(projectId, "Name" + version) - .WithProjectAssemblyName(projectId, "AssemblyName" + version) - .WithProjectFilePath(projectId, "FilePath" + version) - .WithProjectOutputFilePath(projectId, "OutputFilePath" + version) - .WithProjectOutputRefFilePath(projectId, "OutputRefFilePath" + version) - .WithProjectCompilationOutputInfo(projectId, new CompilationOutputInfo("AssemblyPath" + version)) - .WithProjectDefaultNamespace(projectId, "DefaultNamespace" + version) - .WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1 + version) - .WithHasAllInformation(projectId, (version % 2) != 0) - .WithRunAnalyzers(projectId, (version % 2) != 0); - } - - static void ValidateProperties(Solution solution, int version) - { - var project = solution.Projects.Single(); - Assert.Equal("Name" + version, project.Name); - Assert.Equal("AssemblyName" + version, project.AssemblyName); - Assert.Equal("FilePath" + version, project.FilePath); - Assert.Equal("OutputFilePath" + version, project.OutputFilePath); - Assert.Equal("OutputRefFilePath" + version, project.OutputRefFilePath); - Assert.Equal("AssemblyPath" + version, project.CompilationOutputInfo.AssemblyPath); - Assert.Equal("DefaultNamespace" + version, project.DefaultNamespace); - Assert.Equal(SourceHashAlgorithm.Sha1 + version, project.State.ChecksumAlgorithm); - Assert.Equal((version % 2) != 0, project.State.HasAllInformation); - Assert.Equal((version % 2) != 0, project.State.RunAnalyzers); - } - - Assert.True(workspace.SetCurrentSolution(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged)); - - await VerifySolutionUpdate(workspace, - newSolutionGetter: s => SetProjectProperties(s, version: 1), - oldSolutionValidator: s => ValidateProperties(s, version: 0), - newSolutionValidator: s => ValidateProperties(s, version: 1)).ConfigureAwait(false); + var project = solution.Projects.Single(); + Assert.Equal("Name" + version, project.Name); + Assert.Equal("AssemblyName" + version, project.AssemblyName); + Assert.Equal("FilePath" + version, project.FilePath); + Assert.Equal("OutputFilePath" + version, project.OutputFilePath); + Assert.Equal("OutputRefFilePath" + version, project.OutputRefFilePath); + Assert.Equal("AssemblyPath" + version, project.CompilationOutputInfo.AssemblyPath); + Assert.Equal("DefaultNamespace" + version, project.DefaultNamespace); + Assert.Equal(SourceHashAlgorithm.Sha1 + version, project.State.ChecksumAlgorithm); + Assert.Equal((version % 2) != 0, project.State.HasAllInformation); + Assert.Equal((version % 2) != 0, project.State.RunAnalyzers); } - [Fact] - public async Task TestUpdateDocumentInfo() - { - var code = @"class Test { void Method() { } }"; + Assert.True(workspace.SetCurrentSolution(s => SetProjectProperties(s, version: 0), WorkspaceChangeKind.SolutionChanged)); - await VerifySolutionUpdate(code, s => s.WithDocumentFolders(s.Projects.First().Documents.First().Id, new[] { "test" })); - } + await VerifySolutionUpdate(workspace, + newSolutionGetter: s => SetProjectProperties(s, version: 1), + oldSolutionValidator: s => ValidateProperties(s, version: 0), + newSolutionValidator: s => ValidateProperties(s, version: 1)).ConfigureAwait(false); + } + + [Fact] + public async Task TestUpdateDocumentInfo() + { + var code = @"class Test { void Method() { } }"; - [Fact] - public async Task TestAddUpdateRemoveProjects() + await VerifySolutionUpdate(code, s => s.WithDocumentFolders(s.Projects.First().Documents.First().Id, new[] { "test" })); + } + + [Fact] + public async Task TestAddUpdateRemoveProjects() + { + var code = @"class Test { void Method() { } }"; + + await VerifySolutionUpdate(code, s => { - var code = @"class Test { void Method() { } }"; + var existingProjectId = s.ProjectIds.First(); - await VerifySolutionUpdate(code, s => - { - var existingProjectId = s.ProjectIds.First(); + s = s.AddProject("newProject", "newProject", LanguageNames.CSharp).Solution; - s = s.AddProject("newProject", "newProject", LanguageNames.CSharp).Solution; + var project = s.GetProject(existingProjectId); + project = project.WithCompilationOptions(project.CompilationOptions.WithModuleName("modified")); - var project = s.GetProject(existingProjectId); - project = project.WithCompilationOptions(project.CompilationOptions.WithModuleName("modified")); + var existingDocumentId = project.DocumentIds.First(); - var existingDocumentId = project.DocumentIds.First(); + project = project.AddDocument("newDocument", SourceText.From("// new text")).Project; - project = project.AddDocument("newDocument", SourceText.From("// new text")).Project; + var document = project.GetDocument(existingDocumentId); - var document = project.GetDocument(existingDocumentId); + document = document.WithSourceCodeKind(SourceCodeKind.Script); - document = document.WithSourceCodeKind(SourceCodeKind.Script); + return document.Project.Solution; + }); + } - return document.Project.Solution; - }); - } + [Fact] + public async Task TestAdditionalDocument() + { + var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + + var projectId = workspace.CurrentSolution.ProjectIds.First(); + var additionalDocumentId = DocumentId.CreateNewId(projectId); + var additionalDocumentInfo = DocumentInfo.Create( + additionalDocumentId, "additionalFile", + loader: TextLoader.From(TextAndVersion.Create(SourceText.From("test"), VersionStamp.Create()))); - [Fact] - public async Task TestAdditionalDocument() + await VerifySolutionUpdate(workspace, s => { - var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - - var projectId = workspace.CurrentSolution.ProjectIds.First(); - var additionalDocumentId = DocumentId.CreateNewId(projectId); - var additionalDocumentInfo = DocumentInfo.Create( - additionalDocumentId, "additionalFile", - loader: TextLoader.From(TextAndVersion.Create(SourceText.From("test"), VersionStamp.Create()))); - - await VerifySolutionUpdate(workspace, s => - { - return s.AddAdditionalDocument(additionalDocumentInfo); - }); - - workspace.OnAdditionalDocumentAdded(additionalDocumentInfo); - - await VerifySolutionUpdate(workspace, s => - { - return s.WithAdditionalDocumentText(additionalDocumentId, SourceText.From("changed")); - }); - - await VerifySolutionUpdate(workspace, s => - { - return s.RemoveAdditionalDocument(additionalDocumentId); - }); - } + return s.AddAdditionalDocument(additionalDocumentInfo); + }); - [Fact] - public async Task TestAnalyzerConfigDocument() + workspace.OnAdditionalDocumentAdded(additionalDocumentInfo); + + await VerifySolutionUpdate(workspace, s => { - var configPath = Path.Combine(Path.GetTempPath(), ".editorconfig"); - var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - - var projectId = workspace.CurrentSolution.ProjectIds.First(); - var analyzerConfigDocumentId = DocumentId.CreateNewId(projectId); - var analyzerConfigDocumentInfo = DocumentInfo.Create( - analyzerConfigDocumentId, - name: ".editorconfig", - loader: TextLoader.From(TextAndVersion.Create(SourceText.From("root = true"), VersionStamp.Create(), filePath: configPath)), - filePath: configPath); - - await VerifySolutionUpdate(workspace, s => - { - return s.AddAnalyzerConfigDocuments(ImmutableArray.Create(analyzerConfigDocumentInfo)); - }); - - workspace.OnAnalyzerConfigDocumentAdded(analyzerConfigDocumentInfo); - - await VerifySolutionUpdate(workspace, s => - { - return s.WithAnalyzerConfigDocumentText(analyzerConfigDocumentId, SourceText.From("root = false")); - }); - - await VerifySolutionUpdate(workspace, s => - { - return s.RemoveAnalyzerConfigDocument(analyzerConfigDocumentId); - }); - } + return s.WithAdditionalDocumentText(additionalDocumentId, SourceText.From("changed")); + }); - [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] - public async Task TestDocument() + await VerifySolutionUpdate(workspace, s => { - var code = @"class Test { void Method() { } }"; + return s.RemoveAdditionalDocument(additionalDocumentId); + }); + } + + [Fact] + public async Task TestAnalyzerConfigDocument() + { + var configPath = Path.Combine(Path.GetTempPath(), ".editorconfig"); + var code = @"class Test { void Method() { } }"; + using var workspace = TestWorkspace.CreateCSharp(code); + + var projectId = workspace.CurrentSolution.ProjectIds.First(); + var analyzerConfigDocumentId = DocumentId.CreateNewId(projectId); + var analyzerConfigDocumentInfo = DocumentInfo.Create( + analyzerConfigDocumentId, + name: ".editorconfig", + loader: TextLoader.From(TextAndVersion.Create(SourceText.From("root = true"), VersionStamp.Create(), filePath: configPath)), + filePath: configPath); + + await VerifySolutionUpdate(workspace, s => + { + return s.AddAnalyzerConfigDocuments(ImmutableArray.Create(analyzerConfigDocumentInfo)); + }); - using var workspace = TestWorkspace.CreateCSharp(code); + workspace.OnAnalyzerConfigDocumentAdded(analyzerConfigDocumentInfo); - var projectId = workspace.CurrentSolution.ProjectIds.First(); - var documentId = DocumentId.CreateNewId(projectId); - var documentInfo = DocumentInfo.Create( - documentId, "sourceFile", - loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class A { }"), VersionStamp.Create()))); + await VerifySolutionUpdate(workspace, s => + { + return s.WithAnalyzerConfigDocumentText(analyzerConfigDocumentId, SourceText.From("root = false")); + }); - await VerifySolutionUpdate(workspace, s => - { - return s.AddDocument(documentInfo); - }); + await VerifySolutionUpdate(workspace, s => + { + return s.RemoveAnalyzerConfigDocument(analyzerConfigDocumentId); + }); + } - workspace.OnDocumentAdded(documentInfo); + [Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)] + public async Task TestDocument() + { + var code = @"class Test { void Method() { } }"; - await VerifySolutionUpdate(workspace, s => - { - return s.WithDocumentText(documentId, SourceText.From("class Changed { }")); - }); + using var workspace = TestWorkspace.CreateCSharp(code); - await VerifySolutionUpdate(workspace, s => - { - return s.RemoveDocument(documentId); - }); - } + var projectId = workspace.CurrentSolution.ProjectIds.First(); + var documentId = DocumentId.CreateNewId(projectId); + var documentInfo = DocumentInfo.Create( + documentId, "sourceFile", + loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class A { }"), VersionStamp.Create()))); - [Fact] - public async Task TestRemoteWorkspace() + await VerifySolutionUpdate(workspace, s => { - var code = @"class Test { void Method() { } }"; + return s.AddDocument(documentInfo); + }); - // create base solution - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + workspace.OnDocumentAdded(documentInfo); - // create solution service - var solution1 = workspace.CurrentSolution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution1); + await VerifySolutionUpdate(workspace, s => + { + return s.WithDocumentText(documentId, SourceText.From("class Changed { }")); + }); - var remoteSolution1 = await GetInitialOOPSolutionAsync(remoteWorkspace, assetProvider, solution1); + await VerifySolutionUpdate(workspace, s => + { + return s.RemoveDocument(documentId); + }); + } - await Verify(remoteWorkspace, solution1, remoteSolution1); + [Fact] + public async Task TestRemoteWorkspace() + { + var code = @"class Test { void Method() { } }"; - // update remote workspace - var currentSolution = remoteSolution1.WithDocumentText(remoteSolution1.Projects.First().Documents.First().Id, SourceText.From(code + " class Test2 { }")); - var oopSolution2 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(currentSolution); + // create base solution + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - await Verify(remoteWorkspace, currentSolution, oopSolution2); + // create solution service + var solution1 = workspace.CurrentSolution; + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution1); - // move backward - await Verify(remoteWorkspace, remoteSolution1, await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(remoteSolution1)); + var remoteSolution1 = await GetInitialOOPSolutionAsync(remoteWorkspace, assetProvider, solution1); - // move forward - currentSolution = oopSolution2.WithDocumentText(oopSolution2.Projects.First().Documents.First().Id, SourceText.From(code + " class Test3 { }")); - var remoteSolution3 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(currentSolution); + await Verify(remoteWorkspace, solution1, remoteSolution1); - await Verify(remoteWorkspace, currentSolution, remoteSolution3); + // update remote workspace + var currentSolution = remoteSolution1.WithDocumentText(remoteSolution1.Projects.First().Documents.First().Id, SourceText.From(code + " class Test2 { }")); + var oopSolution2 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(currentSolution); - // move to new solution backward - var solutionInfo2 = await assetProvider.CreateSolutionInfoAsync(await solution1.CompilationState.GetChecksumAsync(CancellationToken.None), CancellationToken.None); - var solution2 = remoteWorkspace.GetTestAccessor().CreateSolutionFromInfo(solutionInfo2); + await Verify(remoteWorkspace, currentSolution, oopSolution2); - // move to new solution forward - var solution3 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(solution2); - Assert.NotNull(solution3); - await Verify(remoteWorkspace, solution1, solution3); + // move backward + await Verify(remoteWorkspace, remoteSolution1, await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(remoteSolution1)); - static async Task GetInitialOOPSolutionAsync(RemoteWorkspace remoteWorkspace, AssetProvider assetProvider, Solution solution) - { - // set up initial solution - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); + // move forward + currentSolution = oopSolution2.WithDocumentText(oopSolution2.Projects.First().Documents.First().Id, SourceText.From(code + " class Test3 { }")); + var remoteSolution3 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(currentSolution); - // get solution in remote host - return await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - } + await Verify(remoteWorkspace, currentSolution, remoteSolution3); - static async Task Verify(RemoteWorkspace remoteWorkspace, Solution givenSolution, Solution remoteSolution) - { - // verify we got solution expected - Assert.Equal(await givenSolution.CompilationState.GetChecksumAsync(CancellationToken.None), await remoteSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + // move to new solution backward + var solutionInfo2 = await assetProvider.CreateSolutionInfoAsync(await solution1.CompilationState.GetChecksumAsync(CancellationToken.None), CancellationToken.None); + var solution2 = remoteWorkspace.GetTestAccessor().CreateSolutionFromInfo(solutionInfo2); - // verify remote workspace got updated - Assert.Equal(remoteSolution, remoteWorkspace.CurrentSolution); - } - } + // move to new solution forward + var solution3 = await remoteWorkspace.GetTestAccessor().UpdateWorkspaceCurrentSolutionAsync(solution2); + Assert.NotNull(solution3); + await Verify(remoteWorkspace, solution1, solution3); - [Theory, CombinatorialData] - [WorkItem("https://github.com/dotnet/roslyn/issues/48564")] - public async Task TestAddingProjectsWithExplicitOptions(bool useDefaultOptionValue) + static async Task GetInitialOOPSolutionAsync(RemoteWorkspace remoteWorkspace, AssetProvider assetProvider, Solution solution) { - using var workspace = TestWorkspace.CreateCSharp(@"public class C { }"); - using var remoteWorkspace = CreateRemoteWorkspace(); - - // Initial empty solution - var solution = workspace.CurrentSolution; - solution = solution.RemoveProject(solution.ProjectIds.Single()); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + // set up initial solution var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - - // Add a C# project and a VB project, set some options, and check again - var csharpDocument = new TestHostDocument("public class C { }"); - var csharpProject = new TestHostProject(workspace, csharpDocument, language: LanguageNames.CSharp, name: "project2"); - var csharpProjectInfo = csharpProject.ToProjectInfo(); - - var vbDocument = new TestHostDocument("Public Class D \r\n Inherits C\r\nEnd Class"); - var vbProject = new TestHostProject(workspace, vbDocument, language: LanguageNames.VisualBasic, name: "project3"); - var vbProjectInfo = vbProject.ToProjectInfo(); - - solution = solution.AddProject(csharpProjectInfo).AddProject(vbProjectInfo); - var newOptionValue = useDefaultOptionValue - ? FormattingOptions2.NewLine.DefaultValue - : FormattingOptions2.NewLine.DefaultValue + FormattingOptions2.NewLine.DefaultValue; - solution = solution.WithOptions(solution.Options - .WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, newOptionValue) - .WithChangedOption(FormattingOptions.NewLine, LanguageNames.VisualBasic, newOptionValue)); - - assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); + + // get solution in remote host + return await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); } - [Fact] - public async Task TestFrozenSourceGeneratedDocument() + static async Task Verify(RemoteWorkspace remoteWorkspace, Solution givenSolution, Solution remoteSolution) { - using var workspace = TestWorkspace.CreateCSharp(@""); - using var remoteWorkspace = CreateRemoteWorkspace(); - - var solution = workspace.CurrentSolution - .Projects.Single() - .AddAnalyzerReference(new AnalyzerFileReference(typeof(Microsoft.CodeAnalysis.TestSourceGenerator.HelloWorldGenerator).Assembly.Location, new TestAnalyzerAssemblyLoader())) - .Solution; + // verify we got solution expected + Assert.Equal(await givenSolution.CompilationState.GetChecksumAsync(CancellationToken.None), await remoteSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - // First sync the solution over that has a generator - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - - // Now freeze with some content - var documentIdentity = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).First().Identity; - var frozenText1 = SourceText.From("// Hello, World!"); - var frozenSolution1 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, DateTime.Now, frozenText1).Project.Solution; - - assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution1); - solutionChecksum = await frozenSolution1.CompilationState.GetChecksumAsync(CancellationToken.None); - synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); - - // Try freezing with some different content from the original solution - var frozenText2 = SourceText.From("// Hello, World! A second time!"); - var frozenSolution2 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, DateTime.Now, frozenText2).Project.Solution; - - assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution2); - solutionChecksum = await frozenSolution2.CompilationState.GetChecksumAsync(CancellationToken.None); - synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + // verify remote workspace got updated + Assert.Equal(remoteSolution, remoteWorkspace.CurrentSolution); } + } - [Fact] - public async Task TestPartialProjectSync_GetSolutionFirst() - { - var code = @"class Test { void Method() { } }"; + [Theory, CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/48564")] + public async Task TestAddingProjectsWithExplicitOptions(bool useDefaultOptionValue) + { + using var workspace = TestWorkspace.CreateCSharp(@"public class C { }"); + using var remoteWorkspace = CreateRemoteWorkspace(); + + // Initial empty solution + var solution = workspace.CurrentSolution; + solution = solution.RemoveProject(solution.ProjectIds.Single()); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // Add a C# project and a VB project, set some options, and check again + var csharpDocument = new TestHostDocument("public class C { }"); + var csharpProject = new TestHostProject(workspace, csharpDocument, language: LanguageNames.CSharp, name: "project2"); + var csharpProjectInfo = csharpProject.ToProjectInfo(); + + var vbDocument = new TestHostDocument("Public Class D \r\n Inherits C\r\nEnd Class"); + var vbProject = new TestHostProject(workspace, vbDocument, language: LanguageNames.VisualBasic, name: "project3"); + var vbProjectInfo = vbProject.ToProjectInfo(); + + solution = solution.AddProject(csharpProjectInfo).AddProject(vbProjectInfo); + var newOptionValue = useDefaultOptionValue + ? FormattingOptions2.NewLine.DefaultValue + : FormattingOptions2.NewLine.DefaultValue + FormattingOptions2.NewLine.DefaultValue; + solution = solution.WithOptions(solution.Options + .WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, newOptionValue) + .WithChangedOption(FormattingOptions.NewLine, LanguageNames.VisualBasic, newOptionValue)); + + assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + } - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + [Fact] + public async Task TestFrozenSourceGeneratedDocument() + { + using var workspace = TestWorkspace.CreateCSharp(@""); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution + .Projects.Single() + .AddAnalyzerReference(new AnalyzerFileReference(typeof(Microsoft.CodeAnalysis.TestSourceGenerator.HelloWorldGenerator).Assembly.Location, new TestAnalyzerAssemblyLoader())) + .Solution; + + // First sync the solution over that has a generator + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // Now freeze with some content + var documentIdentity = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).First().Identity; + var frozenText1 = SourceText.From("// Hello, World!"); + var frozenSolution1 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, DateTime.Now, frozenText1).Project.Solution; + + assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution1); + solutionChecksum = await frozenSolution1.CompilationState.GetChecksumAsync(CancellationToken.None); + synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + + // Try freezing with some different content from the original solution + var frozenText2 = SourceText.From("// Hello, World! A second time!"); + var frozenSolution2 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, DateTime.Now, frozenText2).Project.Solution; + + assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution2); + solutionChecksum = await frozenSolution2.CompilationState.GetChecksumAsync(CancellationToken.None); + synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); + } - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_GetSolutionFirst() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project2.Solution; + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - // Do the initial full sync - await solution.AppendAssetMapAsync(map, CancellationToken.None); + solution = project2.Solution; - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - Assert.Equal(2, syncedFullSolution.Projects.Count()); + // Do the initial full sync + await solution.AppendAssetMapAsync(map, CancellationToken.None); - // Syncing project1 should do nothing as syncing the solution already synced it over. - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project1SyncedSolution.Projects.Count()); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - // Syncing project2 should do nothing as syncing the solution already synced it over. - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project2SyncedSolution.Projects.Count()); - } + Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(2, syncedFullSolution.Projects.Count()); - [Fact] - public async Task TestPartialProjectSync_GetSolutionLast() - { - var code = @"class Test { void Method() { } }"; + // Syncing project1 should do nothing as syncing the solution already synced it over. + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project1SyncedSolution.Projects.Count()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // Syncing project2 should do nothing as syncing the solution already synced it over. + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project2SyncedSolution.Projects.Count()); + } - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_GetSolutionLast() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project2.Solution; + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - // Syncing project 1 should just since it over. - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + solution = project2.Solution; - // Syncing project 2 should end up with only p2 synced over. - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - // then syncing the whole project should now copy both over. - await solution.AppendAssetMapAsync(map, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + // Syncing project 1 should just since it over. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); - Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - Assert.Equal(2, syncedFullSolution.Projects.Count()); - } + // Syncing project 2 should end up with only p2 synced over. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects1() - { - var code = @"class Test { void Method() { } }"; + // then syncing the whole project should now copy both over. + await solution.AppendAssetMapAsync(map, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(2, syncedFullSolution.Projects.Count()); + } - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects1() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); - Assert.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); - - // syncing project 3 should sync project 2 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project3SyncedSolution.Projects.Count()); - } + solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects2() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); + Assert.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); - var solution = workspace.CurrentSolution; + // syncing project 3 should sync project 2 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project3SyncedSolution.Projects.Count()); + } - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects2() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + + solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); + + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + + // syncing P3 should since project P2 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project3SyncedSolution.Projects.Count()); + + // if we then sync just P2, we should still have only P2 in the synced cone + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); + AssertEx.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); + + // if we then sync just P1, we should only have it in its own cone. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + AssertEx.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + } - solution = project3.Solution.AddProjectReference(project3.Id, new(project3.Solution.Projects.Single(p => p.Name == "P2").Id)); + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects3() + { + var code = @"class Test { void Method() { } }"; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - // syncing P3 should since project P2 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project3SyncedSolution.Projects.Count()); + var solution = workspace.CurrentSolution; - // if we then sync just P2, we should still have only P2 in the synced cone - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); - AssertEx.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); - // if we then sync just P1, we should only have it in its own cone. - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - AssertEx.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); - } + solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) + .AddProjectReference(project2.Id, new(project1.Id)); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects3() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // syncing project3 should since project2 and project1 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(3, project3SyncedSolution.Projects.Count()); - var solution = workspace.CurrentSolution; + // syncing project2 should only have it and project 1. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project2SyncedSolution.Projects.Count()); - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + // syncing project1 should only be itself + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + } - solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) - .AddProjectReference(project2.Id, new(project1.Id)); + [Fact] + public async Task TestPartialProjectSync_GetDependentProjects4() + { + var code = @"class Test { void Method() { } }"; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - // syncing project3 should since project2 and project1 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(3, project3SyncedSolution.Projects.Count()); + var solution = workspace.CurrentSolution; - // syncing project2 should only have it and project 1. - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project2SyncedSolution.Projects.Count()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); + var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); - // syncing project1 should only be itself - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - } + solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) + .AddProjectReference(project3.Id, new(project1.Id)); - [Fact] - public async Task TestPartialProjectSync_GetDependentProjects4() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + + // syncing project3 should since project2 and project1 as well because of the p2p ref + await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(3, project3SyncedSolution.Projects.Count()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // Syncing project2 should only have a cone with itself. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); - var solution = workspace.CurrentSolution; + // Syncing project1 should only have a cone with itself. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + } - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - var project3 = project2.Solution.AddProject("P3", "P3", LanguageNames.CSharp); + [Fact] + public async Task TestPartialProjectSync_Options1() + { + var code = @"class Test { void Method() { } }"; - solution = project3.Solution.AddProjectReference(project3.Id, new(project2.Id)) - .AddProjectReference(project3.Id, new(project1.Id)); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var solution = workspace.CurrentSolution; - // syncing project3 should since project2 and project1 as well because of the p2p ref - await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); - var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(3, project3SyncedSolution.Projects.Count()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); - // Syncing project2 should only have a cone with itself. - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); + solution = project2.Solution; - // Syncing project1 should only have a cone with itself. - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - } + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - [Fact] - public async Task TestPartialProjectSync_Options1() - { - var code = @"class Test { void Method() { } }"; + // Syncing over project1 should give us 1 set of options on the OOP side. + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project1SyncedSolution.Projects.Count()); + Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + + // Syncing over project2 should also only be one set of options. + await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(1, project2SyncedSolution.Projects.Count()); + } + + [Fact] + public async Task TestPartialProjectSync_DoesNotSeeChangesOutsideOfCone() + { + var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - var solution = workspace.CurrentSolution; + var solution = workspace.CurrentSolution; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); - solution = project2.Solution; + solution = project2.Solution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - // Syncing over project1 should give us 1 set of options on the OOP side. + // Do the initial full sync + await solution.AppendAssetMapAsync(map, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(2, fullSyncedSolution.Projects.Count()); + + // Mutate both projects to each have a document in it. + solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; + solution = solution.GetProject(project2.Id).AddDocument("Y.vb", SourceText.From("' Y")).Project.Solution; + + // Now just sync project1's cone over. We should not see the change to project2 on the remote side. + // But we will still see project2. + { await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project1SyncedSolution.Projects.Count()); - Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); + Assert.Equal(2, project1SyncedSolution.Projects.Count()); + var csharpProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); + var vbProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); + Assert.True(csharpProject.DocumentIds.Count == 2); + Assert.Empty(vbProject.DocumentIds); + } - // Syncing over project2 should also only be one set of options. + // Similarly, if we sync just project2's cone over: + { await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(1, project2SyncedSolution.Projects.Count()); + Assert.Equal(2, project2SyncedSolution.Projects.Count()); + var csharpProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); + var vbProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); + Assert.Single(csharpProject.DocumentIds); + Assert.Single(vbProject.DocumentIds); } + } - [Fact] - public async Task TestPartialProjectSync_DoesNotSeeChangesOutsideOfCone() - { - var code = @"class Test { void Method() { } }"; - - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); - - var solution = workspace.CurrentSolution; + [Fact] + public async Task TestPartialProjectSync_AddP2PRef() + { + var code = @"class Test { void Method() { } }"; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.VisualBasic); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - solution = project2.Solution; + var solution = workspace.CurrentSolution; - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + var project1 = solution.Projects.Single(); + var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - // Do the initial full sync - await solution.AppendAssetMapAsync(map, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(2, fullSyncedSolution.Projects.Count()); - - // Mutate both projects to each have a document in it. - solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; - solution = solution.GetProject(project2.Id).AddDocument("Y.vb", SourceText.From("' Y")).Project.Solution; - - // Now just sync project1's cone over. We should not see the change to project2 on the remote side. - // But we will still see project2. - { - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project1SyncedSolution.Projects.Count()); - var csharpProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); - var vbProject = project1SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); - Assert.True(csharpProject.DocumentIds.Count == 2); - Assert.Empty(vbProject.DocumentIds); - } - - // Similarly, if we sync just project2's cone over: - { - await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); - var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project2SyncedSolution.Projects.Count()); - var csharpProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.CSharp); - var vbProject = project2SyncedSolution.Projects.Single(p => p.Language == LanguageNames.VisualBasic); - Assert.Single(csharpProject.DocumentIds); - Assert.Single(vbProject.DocumentIds); - } - } + solution = project2.Solution; - [Fact] - public async Task TestPartialProjectSync_AddP2PRef() - { - var code = @"class Test { void Method() { } }"; + var map = new Dictionary(); + var assetProvider = new AssetProvider( + Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + // Do the initial full sync + await solution.AppendAssetMapAsync(map, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); + Assert.Equal(2, fullSyncedSolution.Projects.Count()); - var solution = workspace.CurrentSolution; + // Mutate both projects to have a document in it, and add a p2p ref from project1 to project2 + solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; + solution = solution.GetProject(project2.Id).AddDocument("Y.cs", SourceText.From("// Y")).Project.Solution; + solution = solution.GetProject(project1.Id).AddProjectReference(new ProjectReference(project2.Id)).Solution; - var project1 = solution.Projects.Single(); - var project2 = solution.AddProject("P2", "P2", LanguageNames.CSharp); - - solution = project2.Solution; - - var map = new Dictionary(); - var assetProvider = new AssetProvider( - Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); + // Now just sync project1's cone over. This will validate that the p2p ref doesn't try to add a new + // project, but instead sees the existing one. + { + await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); + Assert.Equal(2, project1SyncedSolution.Projects.Count()); + var project1Synced = project1SyncedSolution.GetRequiredProject(project1.Id); + var project2Synced = project1SyncedSolution.GetRequiredProject(project2.Id); - // Do the initial full sync - await solution.AppendAssetMapAsync(map, CancellationToken.None); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var fullSyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, CancellationToken.None); - Assert.Equal(2, fullSyncedSolution.Projects.Count()); - - // Mutate both projects to have a document in it, and add a p2p ref from project1 to project2 - solution = solution.GetProject(project1.Id).AddDocument("X.cs", SourceText.From("// X")).Project.Solution; - solution = solution.GetProject(project2.Id).AddDocument("Y.cs", SourceText.From("// Y")).Project.Solution; - solution = solution.GetProject(project1.Id).AddProjectReference(new ProjectReference(project2.Id)).Solution; - - // Now just sync project1's cone over. This will validate that the p2p ref doesn't try to add a new - // project, but instead sees the existing one. - { - await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(2, project1SyncedSolution.Projects.Count()); - var project1Synced = project1SyncedSolution.GetRequiredProject(project1.Id); - var project2Synced = project1SyncedSolution.GetRequiredProject(project2.Id); - - Assert.True(project1Synced.DocumentIds.Count == 2); - Assert.Single(project2Synced.DocumentIds); - Assert.Single(project1Synced.ProjectReferences); - } + Assert.True(project1Synced.DocumentIds.Count == 2); + Assert.Single(project2Synced.DocumentIds); + Assert.Single(project1Synced.ProjectReferences); } + } - [Fact] - public async Task TestPartialProjectSync_ReferenceToNonExistentProject() - { - var code = @"class Test { void Method() { } }"; + [Fact] + public async Task TestPartialProjectSync_ReferenceToNonExistentProject() + { + var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code); - using var remoteWorkspace = CreateRemoteWorkspace(); + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); - var solution = workspace.CurrentSolution; + var solution = workspace.CurrentSolution; - var project1 = solution.Projects.Single(); + var project1 = solution.Projects.Single(); - // This reference a project that doesn't exist. - // Ensure that it's still fine to get the checksum for this project we have. - project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); + // This reference a project that doesn't exist. + // Ensure that it's still fine to get the checksum for this project we have. + project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); - solution = project1.Solution; + solution = project1.Solution; - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); - } + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + } - private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) - { - using var workspace = TestWorkspace.CreateCSharp(code); - await VerifySolutionUpdate(workspace, newSolutionGetter); - } + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) + { + using var workspace = TestWorkspace.CreateCSharp(code); + await VerifySolutionUpdate(workspace, newSolutionGetter); + } - private static async Task VerifySolutionUpdate( - TestWorkspace workspace, - Func newSolutionGetter, - Action oldSolutionValidator = null, - Action newSolutionValidator = null) - { - var solution = workspace.CurrentSolution; - oldSolutionValidator?.Invoke(solution); + private static async Task VerifySolutionUpdate( + TestWorkspace workspace, + Func newSolutionGetter, + Action oldSolutionValidator = null, + Action newSolutionValidator = null) + { + var solution = workspace.CurrentSolution; + oldSolutionValidator?.Invoke(solution); - var map = new Dictionary(); + var map = new Dictionary(); - using var remoteWorkspace = CreateRemoteWorkspace(); - var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution, map); - var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + using var remoteWorkspace = CreateRemoteWorkspace(); + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution, map); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - // update primary workspace - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); - var recoveredSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - oldSolutionValidator?.Invoke(recoveredSolution); + // update primary workspace + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, CancellationToken.None); + var recoveredSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + oldSolutionValidator?.Invoke(recoveredSolution); - Assert.Equal(WorkspaceKind.RemoteWorkspace, recoveredSolution.WorkspaceKind); - Assert.Equal(solutionChecksum, await recoveredSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(WorkspaceKind.RemoteWorkspace, recoveredSolution.WorkspaceKind); + Assert.Equal(solutionChecksum, await recoveredSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - // get new solution - var newSolution = newSolutionGetter(solution); - var newSolutionChecksum = await newSolution.CompilationState.GetChecksumAsync(CancellationToken.None); - await newSolution.AppendAssetMapAsync(map, CancellationToken.None); + // get new solution + var newSolution = newSolutionGetter(solution); + var newSolutionChecksum = await newSolution.CompilationState.GetChecksumAsync(CancellationToken.None); + await newSolution.AppendAssetMapAsync(map, CancellationToken.None); - // get solution without updating primary workspace - var recoveredNewSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + // get solution without updating primary workspace + var recoveredNewSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(newSolutionChecksum, await recoveredNewSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(newSolutionChecksum, await recoveredNewSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); - // do same once updating primary workspace - await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, newSolutionChecksum, CancellationToken.None); - var third = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + // do same once updating primary workspace + await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, newSolutionChecksum, CancellationToken.None); + var third = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, CancellationToken.None); - Assert.Equal(newSolutionChecksum, await third.CompilationState.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(newSolutionChecksum, await third.CompilationState.GetChecksumAsync(CancellationToken.None)); - newSolutionValidator?.Invoke(recoveredNewSolution); - } + newSolutionValidator?.Invoke(recoveredNewSolution); + } - private static async Task GetAssetProviderAsync(Workspace workspace, RemoteWorkspace remoteWorkspace, Solution solution, Dictionary map = null) - { - // make sure checksum is calculated - await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + private static async Task GetAssetProviderAsync(Workspace workspace, RemoteWorkspace remoteWorkspace, Solution solution, Dictionary map = null) + { + // make sure checksum is calculated + await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - map ??= []; - await solution.AppendAssetMapAsync(map, CancellationToken.None); + map ??= []; + await solution.AppendAssetMapAsync(map, CancellationToken.None); - var sessionId = Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())); - var storage = new SolutionAssetCache(); - var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); + var sessionId = Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())); + var storage = new SolutionAssetCache(); + var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); - return new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); - } + return new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs index 5d27541525254..7f2e7b27674a3 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs @@ -3,112 +3,118 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger; -namespace Microsoft.CodeAnalysis.Remote +namespace Microsoft.CodeAnalysis.Remote; + +/// +/// This service is used by the SolutionChecksumUpdater to proactively update the solution snapshot in the +/// out-of-process workspace. We do this to limit the amount of time required to synchronize a solution over after +/// an edit once a feature is asking for a snapshot. +/// +internal sealed class RemoteAssetSynchronizationService(in BrokeredServiceBase.ServiceConstructionArguments arguments) + : BrokeredServiceBase(in arguments), IRemoteAssetSynchronizationService { - /// - /// This service is used by the SolutionChecksumUpdater to proactively update the solution snapshot in the - /// out-of-process workspace. We do this to limit the amount of time required to synchronize a solution over after - /// an edit once a feature is asking for a snapshot. - /// - internal sealed class RemoteAssetSynchronizationService : BrokeredServiceBase, IRemoteAssetSynchronizationService + internal sealed class Factory : FactoryBase { - internal sealed class Factory : FactoryBase - { - protected override IRemoteAssetSynchronizationService CreateService(in ServiceConstructionArguments arguments) - => new RemoteAssetSynchronizationService(in arguments); - } + protected override IRemoteAssetSynchronizationService CreateService(in ServiceConstructionArguments arguments) + => new RemoteAssetSynchronizationService(in arguments); + } - public RemoteAssetSynchronizationService(in ServiceConstructionArguments arguments) - : base(in arguments) + public ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken) + { + return RunServiceAsync(async cancellationToken => { - } + using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizePrimaryWorkspaceAsync, Checksum.GetChecksumLogInfo, solutionChecksum, cancellationToken)) + { + var workspace = GetWorkspace(); + var assetProvider = workspace.CreateAssetProvider(solutionChecksum, WorkspaceManager.SolutionAssetCache, SolutionAssetSource); + await workspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, cancellationToken).ConfigureAwait(false); + } + }, cancellationToken); + } + + public ValueTask SynchronizeActiveDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken) + { + var documentTrackingService = GetWorkspace().Services.GetRequiredService() as RemoteDocumentTrackingService; + if (documentTrackingService is null) + return ValueTaskFactory.CompletedTask; - public ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, CancellationToken cancellationToken) + documentTrackingService.SetActiveDocument(documentId); + } + + public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, ImmutableArray textChanges, CancellationToken cancellationToken) + { + return RunServiceAsync(async cancellationToken => { - return RunServiceAsync(async cancellationToken => + var workspace = GetWorkspace(); + + using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizeTextAsync, Checksum.GetChecksumLogInfo, baseTextChecksum, cancellationToken)) { - using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizePrimaryWorkspaceAsync, Checksum.GetChecksumLogInfo, solutionChecksum, cancellationToken)) + var serializer = workspace.Services.GetRequiredService(); + + // Try to get the text associated with baseTextChecksum + var text = await TryGetSourceTextAsync(WorkspaceManager, workspace, documentId, baseTextChecksum, cancellationToken).ConfigureAwait(false); + if (text == null) { - var workspace = GetWorkspace(); - var assetProvider = workspace.CreateAssetProvider(solutionChecksum, WorkspaceManager.SolutionAssetCache, SolutionAssetSource); - await workspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, cancellationToken).ConfigureAwait(false); + // it won't bring in base text if it is not there already. + // text needed will be pulled in when there is request + return; } - }, cancellationToken); - } - public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, IEnumerable textChanges, CancellationToken cancellationToken) - { - return RunServiceAsync(async cancellationToken => - { - var workspace = GetWorkspace(); + // Now attempt to manually apply the edit, producing the new forked text. Store that directly in + // the asset cache so that future calls to retrieve it can do so quickly, without synchronizing over + // the entire document. + var newText = text.WithChanges(textChanges); + var newSerializableText = new SerializableSourceText(newText, newText.GetContentHash()); + var newChecksum = serializer.CreateChecksum(newSerializableText, cancellationToken); + + WorkspaceManager.SolutionAssetCache.GetOrAdd(newChecksum, newSerializableText); + } - using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizeTextAsync, Checksum.GetChecksumLogInfo, baseTextChecksum, cancellationToken)) + return; + + async static Task TryGetSourceTextAsync( + RemoteWorkspaceManager workspaceManager, + Workspace workspace, + DocumentId documentId, + Checksum baseTextChecksum, + CancellationToken cancellationToken) + { + // check the cheap and fast one first. + // see if the cache has the source text + if (workspaceManager.SolutionAssetCache.TryGetAsset(baseTextChecksum, out var serializableSourceText)) { - var serializer = workspace.Services.GetRequiredService(); - - // Try to get the text associated with baseTextChecksum - var text = await TryGetSourceTextAsync(WorkspaceManager, workspace, documentId, baseTextChecksum, cancellationToken).ConfigureAwait(false); - if (text == null) - { - // it won't bring in base text if it is not there already. - // text needed will be pulled in when there is request - return; - } - - // Now attempt to manually apply the edit, producing the new forked text. Store that directly in - // the asset cache so that future calls to retrieve it can do so quickly, without synchronizing over - // the entire document. - var newText = text.WithChanges(textChanges); - var newSerializableText = new SerializableSourceText(newText, newText.GetContentHash()); - var newChecksum = serializer.CreateChecksum(newSerializableText, cancellationToken); - - WorkspaceManager.SolutionAssetCache.GetOrAdd(newChecksum, newSerializableText); + return await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); } - return; + // do slower one + // check whether existing solution has it + var document = workspace.CurrentSolution.GetDocument(documentId); + if (document == null) + { + return null; + } - async static Task TryGetSourceTextAsync( - RemoteWorkspaceManager workspaceManager, - Workspace workspace, - DocumentId documentId, - Checksum baseTextChecksum, - CancellationToken cancellationToken) + // check checksum whether it is there. + // since we lazily synchronize whole solution (SynchronizePrimaryWorkspaceAsync) when things are idle, + // soon or later this will get hit even if text changes got out of sync due to issues in VS side + // such as file is first opened and there is no SourceText in memory yet. + if (!document.State.TryGetStateChecksums(out var state) || + !state.Text.Equals(baseTextChecksum)) { - // check the cheap and fast one first. - // see if the cache has the source text - if (workspaceManager.SolutionAssetCache.TryGetAsset(baseTextChecksum, out var serializableSourceText)) - { - return await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); - } - - // do slower one - // check whether existing solution has it - var document = workspace.CurrentSolution.GetDocument(documentId); - if (document == null) - { - return null; - } - - // check checksum whether it is there. - // since we lazily synchronize whole solution (SynchronizePrimaryWorkspaceAsync) when things are idle, - // soon or later this will get hit even if text changes got out of sync due to issues in VS side - // such as file is first opened and there is no SourceText in memory yet. - if (!document.State.TryGetStateChecksums(out var state) || - !state.Text.Equals(baseTextChecksum)) - { - return null; - } - - return await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + return null; } - }, cancellationToken); - } + + return await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + } + }, cancellationToken); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs index 1a7d27770edc5..4a83c398ac9b6 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs @@ -5,8 +5,6 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.Diagnostics; -using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.Remote; @@ -16,25 +14,19 @@ namespace Microsoft.CodeAnalysis.Remote; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class RemoteDocumentTrackingService() : IDocumentTrackingService { - public bool SupportsDocumentTracking => true; + private DocumentId? _activeDocument; - public event EventHandler ActiveDocumentChanged { add { } remove { } } + public event EventHandler? ActiveDocumentChanged; public ImmutableArray GetVisibleDocuments() => []; public DocumentId? TryGetActiveDocument() - { - Fail("Code should not be attempting to obtain active document from a stateless remote invocation."); - return null; - } + => _activeDocument; - private static void Fail(string message) + internal void SetActiveDocument(DocumentId? documentId) { - // assert in debug builds to hopefully catch problems in CI - Debug.Fail(message); - - // record NFW to see who violates contract. - FatalError.ReportAndCatch(new InvalidOperationException(message)); + _activeDocument = documentId; + ActiveDocumentChanged?.Invoke(this, documentId); } } From f23e03eb70c3864a30204b9d7250ac6f77028447 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 13:55:40 -0700 Subject: [PATCH 053/292] tests and simplification --- .../NavigateTo/NavigateToSearcherTests.cs | 2 +- .../Core/Remote/SolutionChecksumUpdater.cs | 22 ++-- .../NavigateTo/AbstractNavigateToTests.cs | 2 +- ...ctiveAndVisibleDocumentTrackingService.cs} | 13 +-- .../Services/SolutionServiceTests.cs | 104 +++++++++++++++++- .../Solution/Solution_SemanticModelCaching.cs | 12 +- 6 files changed, 120 insertions(+), 35 deletions(-) rename src/EditorFeatures/TestUtilities/{NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs => Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs} (69%) diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index 24c066414e32d..7092c2679be22 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.NavigateTo [Trait(Traits.Feature, Traits.Features.NavigateTo)] public sealed class NavigateToSearcherTests { - private static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(FirstDocIsActiveAndVisibleDocumentTrackingService.Factory)); + private static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); private static void SetupSearchProject( Mock searchService, diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index d0c707add134e..53322e1d31de7 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -35,7 +35,7 @@ internal sealed class SolutionChecksumUpdater /// operations (only syncing text changes) we don't cancel this when we enter the paused state. We simply don't /// start queuing more requests into this until we become unpaused. /// - private readonly AsyncBatchingWorkQueue<(Document? oldDocument, Document? newDocument)> _textChangeQueue; + private readonly AsyncBatchingWorkQueue<(Document oldDocument, Document newDocument)> _textChangeQueue; /// /// Queue for kicking off the work to synchronize the primary workspace's solution. @@ -62,7 +62,7 @@ public SolutionChecksumUpdater( _workspace = workspace; _documentTrackingService = workspace.Services.GetRequiredService(); - _textChangeQueue = new AsyncBatchingWorkQueue<(Document? oldDocument, Document? newDocument)>( + _textChangeQueue = new AsyncBatchingWorkQueue<(Document oldDocument, Document newDocument)>( DelayTimeSpan.NearImmediate, SynchronizeTextChangesAsync, listener, @@ -152,7 +152,10 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs e) if (e.Kind == WorkspaceChangeKind.DocumentChanged) { - _textChangeQueue.AddWork((e.OldSolution.GetDocument(e.DocumentId), e.NewSolution.GetDocument(e.DocumentId))); + var oldDocument = e.OldSolution.GetDocument(e.DocumentId); + var newDocument = e.NewSolution.GetDocument(e.DocumentId); + if (oldDocument != null && newDocument != null) + _textChangeQueue.AddWork((oldDocument, newDocument)); } _synchronizeWorkspaceQueue.AddWork(); @@ -187,19 +190,16 @@ private async ValueTask SynchronizeActiveDocumentAsync(CancellationToken cancell var solution = _workspace.CurrentSolution; await client.TryInvokeAsync( - (service, cancellationToken) => service.SynchronizeActiveDocumentAsync(activeDocument), + (service, cancellationToken) => service.SynchronizeActiveDocumentAsync(activeDocument, cancellationToken), cancellationToken).ConfigureAwait(false); } private async ValueTask SynchronizeTextChangesAsync( - ImmutableSegmentedList<(Document? oldDocument, Document? newDocument)> values, + ImmutableSegmentedList<(Document oldDocument, Document newDocument)> values, CancellationToken cancellationToken) { foreach (var (oldDocument, newDocument) in values) { - if (oldDocument is null || newDocument is null) - continue; - cancellationToken.ThrowIfCancellationRequested(); await SynchronizeTextChangesAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false); } @@ -232,15 +232,15 @@ async ValueTask SynchronizeTextChangesAsync(Document oldDocument, Document newDo } // get text changes - var textChanges = newText.GetTextChanges(oldText); - if (textChanges.Count == 0) + var textChanges = newText.GetTextChanges(oldText).AsImmutable(); + if (textChanges.Length == 0) { // no changes return; } // whole document case - if (textChanges.Count == 1 && textChanges[0].Span.Length == oldText.Length) + if (textChanges.Length == 1 && textChanges[0].Span.Length == oldText.Length) { // no benefit here. pulling from remote host is more efficient return; diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index b9f70d5b16fc5..f494a0f9f0969 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -41,7 +41,7 @@ public abstract partial class AbstractNavigateToTests { protected static readonly TestComposition DefaultComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService)); protected static readonly TestComposition FirstVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocIsVisibleDocumentTrackingService.Factory)); - protected static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocIsActiveAndVisibleDocumentTrackingService.Factory)); + protected static readonly TestComposition FirstActiveAndVisibleComposition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceNavigateToSearchHostService), typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); protected INavigateToItemProvider _provider; protected NavigateToTestAggregator _aggregator; diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs similarity index 69% rename from src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs rename to src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs index 08628f9c6e68e..55fc88be78ef8 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/FirstDocIsActiveAndVisibleDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs @@ -10,26 +10,23 @@ using Microsoft.CodeAnalysis.Host.Mef; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigateTo; +namespace Microsoft.CodeAnalysis.Test.Utilities; -internal sealed class FirstDocIsActiveAndVisibleDocumentTrackingService : IDocumentTrackingService +internal sealed class FirstDocumentIsActiveAndVisibleDocumentTrackingService : IDocumentTrackingService { private readonly Workspace _workspace; [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] - private FirstDocIsActiveAndVisibleDocumentTrackingService(Workspace workspace) + private FirstDocumentIsActiveAndVisibleDocumentTrackingService(Workspace workspace) => _workspace = workspace; - public bool SupportsDocumentTracking => true; - public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } public DocumentId TryGetActiveDocument() => _workspace.CurrentSolution.Projects.First().DocumentIds.First(); public ImmutableArray GetVisibleDocuments() - => ImmutableArray.Create(_workspace.CurrentSolution.Projects.First().DocumentIds.First()); + => [TryGetActiveDocument()]; [ExportWorkspaceServiceFactory(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] public class Factory : IWorkspaceServiceFactory @@ -42,6 +39,6 @@ public Factory() [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new FirstDocIsActiveAndVisibleDocumentTrackingService(workspaceServices.Workspace); + => new FirstDocumentIsActiveAndVisibleDocumentTrackingService(workspaceServices.Workspace); } } diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 2e7750b7f31ed..a8bf873d90ee2 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -13,14 +13,11 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -33,6 +30,9 @@ namespace Roslyn.VisualStudio.Next.UnitTests.Remote; [Trait(Traits.Feature, Traits.Features.RemoteHost)] public class SolutionServiceTests { + private static TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = + FeaturesTestCompositions.Features.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService)); + private static RemoteWorkspace CreateRemoteWorkspace() => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); @@ -858,6 +858,104 @@ public async Task TestPartialProjectSync_ReferenceToNonExistentProject() var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); } + [Fact] + public async Task TestNoActiveDocumentSemanticModelNotCached() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + // Without anything holding onto the semantic model, it should get releases. + var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); + + objectReference.AssertReleased(); + } + + [Fact] + public async Task TestActiveDocumentSemanticModelCached() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code, compilationOptions: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + // Without anything holding onto the semantic model, it should get releases. + var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); + + objectReference.AssertHeld(); + } + + [Fact] + public async Task TestOnlyActiveDocumentSemanticModelCached() + { + using var workspace = TestWorkspace.Create(""" + + + + class Program1 + { + } + + + class Program2 + { + } + + + """, compilationOptions: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.First(); + var document2 = project1.Documents.Last(); + + // Only the semantic model for the active document should be cached. + var objectReference1 = new ObjectReference(await document1.GetSemanticModelAsync()); + var objectReference2 = new ObjectReference(await document2.GetSemanticModelAsync()); + + objectReference1.AssertHeld(); + objectReference1.AssertRelease(); + } + + [Fact] + public async Task TestActiveDocumentSemanticModelCached2() + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); + + // This reference a project that doesn't exist. + // Ensure that it's still fine to get the checksum for this project we have. + project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); + + solution = project1.Solution; + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + } + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) { using var workspace = TestWorkspace.CreateCSharp(code); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs index 3d69d50db5287..970ed107c0b2f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs @@ -2,16 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - namespace Microsoft.CodeAnalysis; -internal partial class Solution +public partial class Solution { /// /// Strongly held reference to the semantic model for the active document. By strongly holding onto it, we ensure @@ -37,9 +30,6 @@ internal partial class Solution internal void OnSemanticModelObtained(DocumentId documentId, SemanticModel semanticModel) { var service = this.Services.GetRequiredService(); - if (!service.SupportsDocumentTracking) - return; - var activeDocumentId = service.TryGetActiveDocument(); if (activeDocumentId != documentId) return; From adb0f2eb4a4b4d952b9251d1e6c25f86cc807826 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 13:57:40 -0700 Subject: [PATCH 054/292] Fixes --- .../Core/Test.Next/Services/ServiceHubServicesTests.cs | 2 +- .../Core/Test.Next/Services/SolutionServiceTests.cs | 6 +++--- .../RemoteAssetSynchronizationService.cs | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 1a9747fc226a7..893cc20f5e94f 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -91,7 +91,7 @@ public async Task TestRemoteHostTextSynchronize() // sync await client.TryInvokeAsync( - (service, cancellationToken) => service.SynchronizeTextAsync(oldDocument.Id, oldState.Text, newText.GetTextChanges(oldText), cancellationToken), + (service, cancellationToken) => service.SynchronizeTextAsync(oldDocument.Id, oldState.Text, newText.GetTextChanges(oldText).AsImmutable(), cancellationToken), CancellationToken.None); // apply change to solution diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index a8bf873d90ee2..fa20a6571a403 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -882,7 +882,7 @@ public async Task TestActiveDocumentSemanticModelCached() { var code = @"class Test { void Method() { } }"; - using var workspace = TestWorkspace.CreateCSharp(code, compilationOptions: s_compositionWithFirstDocumentIsActiveAndVisible); + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); using var remoteWorkspace = CreateRemoteWorkspace(); var solution = workspace.CurrentSolution; @@ -913,7 +913,7 @@ class Program2 } - """, compilationOptions: s_compositionWithFirstDocumentIsActiveAndVisible); + """, composition: s_compositionWithFirstDocumentIsActiveAndVisible); using var remoteWorkspace = CreateRemoteWorkspace(); var solution = workspace.CurrentSolution; @@ -927,7 +927,7 @@ class Program2 var objectReference2 = new ObjectReference(await document2.GetSemanticModelAsync()); objectReference1.AssertHeld(); - objectReference1.AssertRelease(); + objectReference1.AssertReleased(); } [Fact] diff --git a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs index 7f2e7b27674a3..f46c290d40e5d 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs @@ -44,10 +44,8 @@ public ValueTask SynchronizePrimaryWorkspaceAsync(Checksum solutionChecksum, Can public ValueTask SynchronizeActiveDocumentAsync(DocumentId? documentId, CancellationToken cancellationToken) { var documentTrackingService = GetWorkspace().Services.GetRequiredService() as RemoteDocumentTrackingService; - if (documentTrackingService is null) - return ValueTaskFactory.CompletedTask; - - documentTrackingService.SetActiveDocument(documentId); + documentTrackingService?.SetActiveDocument(documentId); + return ValueTaskFactory.CompletedTask; } public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextChecksum, ImmutableArray textChanges, CancellationToken cancellationToken) From 49c30fa41d835d54b746f04d6fa14974c1421996 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 14:05:52 -0700 Subject: [PATCH 055/292] Fix test --- .../Core/Test.Next/Services/SolutionServiceTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index fa20a6571a403..213e979be078e 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -912,6 +912,7 @@ class Program2 { } + """, composition: s_compositionWithFirstDocumentIsActiveAndVisible); using var remoteWorkspace = CreateRemoteWorkspace(); @@ -927,7 +928,7 @@ class Program2 var objectReference2 = new ObjectReference(await document2.GetSemanticModelAsync()); objectReference1.AssertHeld(); - objectReference1.AssertReleased(); + objectReference2.AssertReleased(); } [Fact] From 27aab15f7c2ec26e3b0ccc1ac55572990c8eada0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 14:14:53 -0700 Subject: [PATCH 056/292] Fix test --- .../Services/SolutionServiceTests.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 213e979be078e..e8eb78cdbb173 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -30,8 +30,8 @@ namespace Roslyn.VisualStudio.Next.UnitTests.Remote; [Trait(Traits.Feature, Traits.Features.RemoteHost)] public class SolutionServiceTests { - private static TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = - FeaturesTestCompositions.Features.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService)); + private static readonly TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = + FeaturesTestCompositions.Features.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); private static RemoteWorkspace CreateRemoteWorkspace() => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); @@ -859,7 +859,7 @@ public async Task TestPartialProjectSync_ReferenceToNonExistentProject() } [Fact] - public async Task TestNoActiveDocumentSemanticModelNotCached() + public void TestNoActiveDocumentSemanticModelNotCached() { var code = @"class Test { void Method() { } }"; @@ -872,13 +872,13 @@ public async Task TestNoActiveDocumentSemanticModelNotCached() var document1 = project1.Documents.Single(); // Without anything holding onto the semantic model, it should get releases. - var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); + var objectReference = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); objectReference.AssertReleased(); } [Fact] - public async Task TestActiveDocumentSemanticModelCached() + public void TestActiveDocumentSemanticModelCached() { var code = @"class Test { void Method() { } }"; @@ -890,14 +890,14 @@ public async Task TestActiveDocumentSemanticModelCached() var project1 = solution.Projects.Single(); var document1 = project1.Documents.Single(); - // Without anything holding onto the semantic model, it should get releases. - var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); + // Since this is the active document, we should hold onto it. + var objectReference = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); objectReference.AssertHeld(); } [Fact] - public async Task TestOnlyActiveDocumentSemanticModelCached() + public void TestOnlyActiveDocumentSemanticModelCached() { using var workspace = TestWorkspace.Create(""" @@ -924,8 +924,8 @@ class Program2 var document2 = project1.Documents.Last(); // Only the semantic model for the active document should be cached. - var objectReference1 = new ObjectReference(await document1.GetSemanticModelAsync()); - var objectReference2 = new ObjectReference(await document2.GetSemanticModelAsync()); + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + var objectReference2 = ObjectReference.CreateFromFactory(() => document2.GetSemanticModelAsync().GetAwaiter().GetResult()); objectReference1.AssertHeld(); objectReference2.AssertReleased(); From e49c5b6f1a70c95f6d1464480d78238de14cf3be Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 14:19:55 -0700 Subject: [PATCH 057/292] Add tesT --- .../Services/SolutionServiceTests.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index e8eb78cdbb173..f80e0bd521a6b 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -34,7 +34,7 @@ public class SolutionServiceTests FeaturesTestCompositions.Features.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); private static RemoteWorkspace CreateRemoteWorkspace() - => new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); + => new(FeaturesTestCompositions.RemoteHost.GetHostServices()); [Fact] public async Task TestCreation() @@ -932,7 +932,7 @@ class Program2 } [Fact] - public async Task TestActiveDocumentSemanticModelCached2() + public async Task TestRemoteWorkspaceCachesNothingIfActiveDocumentNotSynced() { var code = @"class Test { void Method() { } }"; @@ -944,17 +944,18 @@ public async Task TestActiveDocumentSemanticModelCached2() var project1 = solution.Projects.Single(); var document1 = project1.Documents.Single(); - var objectReference = new ObjectReference(await document1.GetSemanticModelAsync()); - - // This reference a project that doesn't exist. - // Ensure that it's still fine to get the checksum for this project we have. - project1 = project1.AddProjectReference(new ProjectReference(ProjectId.CreateNewId())); - - solution = project1.Solution; + // Locally the semantic model will be held + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1.AssertHeld(); var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, CancellationToken.None); + + // The remote semantic model will not be held as it doesn't know what the active document is yet. + var objectReference2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference2.AssertReleased(); } private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) From 70832c78c93e7c2727711dbf9f6e17e4f00747de Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 9 Apr 2024 15:13:38 -0700 Subject: [PATCH 058/292] Implement MEF based pull diagnostics --- .../AlwaysActivateInProcLanguageClient.cs | 48 ++-- .../Portable/Diagnostics/DiagnosticKind.cs | 1 + .../Microsoft.CodeAnalysis.Features.csproj | 1 + ...ndContinueDiagnosticSource_OpenDocument.cs | 2 +- ...itAndContinueDiagnosticSource_Workspace.cs | 4 +- .../AbstractPullDiagnosticHandler.cs | 3 +- ...stractWorkspaceDiagnosticSourceProvider.cs | 207 ++++++++++++++++++ ...AbstractWorkspacePullDiagnosticsHandler.cs | 200 ++--------------- .../AbstractDocumentDiagnosticSource.cs | 2 +- .../AbstractProjectDiagnosticSource.cs | 14 +- ...stractWorkspaceDocumentDiagnosticSource.cs | 8 +- .../DiagnosticSourceManager.cs | 91 ++++++++ .../DocumentDiagnosticSource.cs | 7 +- ...ExportDiagnosticSourceProviderAttribute.cs | 20 ++ .../DiagnosticSources/IDiagnosticSource.cs | 4 +- .../IDiagnosticSourceManager.cs | 32 +++ .../IDiagnosticSourceProvider.cs | 34 +++ .../NonLocalDocumentDiagnosticSource.cs | 3 +- .../TaskListDiagnosticSource.cs | 2 +- .../DocumentDiagnosticSourceProvider.cs | 131 +++++++++++ .../DocumentPullDiagnosticHandler.cs | 170 ++++++-------- .../DocumentPullDiagnosticHandlerFactory.cs | 6 +- .../PublicDocumentDiagnosticSourceProvider.cs | 36 +++ ...licDocumentPullDiagnosticHandlerFactory.cs | 8 +- .../PublicDocumentPullDiagnosticsHandler.cs | 21 +- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 23 +- ...PublicWorkspaceDiagnosticSourceProvider.cs | 22 ++ ...icWorkspacePullDiagnosticHandlerFactory.cs | 5 +- .../PublicWorkspacePullDiagnosticsHandler.cs | 15 +- ...cePullDiagnosticsHandler_IOnInitialized.cs | 34 +++ .../WorkspaceDiagnosticSourceProvider.cs | 23 ++ .../WorkspacePullDiagnosticHandler.cs | 74 ++++--- .../WorkspacePullDiagnosticHandlerFactory.cs | 4 +- .../Contracts/IHotReloadDiagnosticService.cs | 18 ++ .../Internal/HotReloadDiagnosticService.cs | 29 +++ .../Internal/HotReloadDiagnosticSource.cs | 30 +++ .../HotReloadDiagnosticSourceProvider.cs | 55 +++++ .../InternalAPI.Unshipped.txt | 16 +- 38 files changed, 989 insertions(+), 414 deletions(-) create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index e4ce2a8eacb29..1eeb0980e9377 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Composition; @@ -40,9 +41,11 @@ internal class AlwaysActivateInProcLanguageClient( ILspServiceLoggerFactory lspLoggerFactory, IThreadingContext threadingContext, ExportProvider exportProvider, + IDiagnosticSourceManager diagnosticSourceManager, [ImportMany] IEnumerable> buildOnlyDiagnostics) : AbstractInProcLanguageClient(lspServiceProvider, globalOptions, lspLoggerFactory, threadingContext, exportProvider) { private readonly ExperimentalCapabilitiesProvider _experimentalCapabilitiesProvider = defaultCapabilitiesProvider; + private readonly IDiagnosticSourceManager _diagnosticSourceManager = diagnosticSourceManager; private readonly IEnumerable> _buildOnlyDiagnostics = buildOnlyDiagnostics; protected override ImmutableArray SupportedLanguages => ProtocolConstants.RoslynLspLanguages; @@ -69,28 +72,35 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.SupportsDiagnosticRequests = true; serverCapabilities.DiagnosticProvider ??= new(); + + // VS does not distinguish between document and workspace diagnostics, so we need to merge them. + var diagnosticSourceNames = _diagnosticSourceManager.GetSourceNames(true) + .Concat(_diagnosticSourceManager.GetSourceNames(false)) + .Distinct(); serverCapabilities.DiagnosticProvider = serverCapabilities.DiagnosticProvider with { SupportsMultipleContextsDiagnostics = true, - DiagnosticKinds = - [ - // Support a specialized requests dedicated to task-list items. This way the client can ask just - // for these, independently of other diagnostics. They can also throttle themselves to not ask if - // the task list would not be visible. - new(PullDiagnosticCategories.Task), - new(PullDiagnosticCategories.EditAndContinue), - // Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on. - new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), - // Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic - // requests, allowing the former to quickly reach the user without blocking on the latter. In a - // similar vein, compiler diagnostics are explicitly distinct from analyzer-diagnostics, allowing - // the former to appear as soon as possible as they are much more critical for the user and should - // not be delayed by a slow analyzer. - new(PullDiagnosticCategories.DocumentCompilerSyntax), - new(PullDiagnosticCategories.DocumentCompilerSemantic), - new(PullDiagnosticCategories.DocumentAnalyzerSyntax), - new(PullDiagnosticCategories.DocumentAnalyzerSemantic), - ], + DiagnosticKinds = diagnosticSourceNames.Select(n => new VSInternalDiagnosticKind(n)).ToArray(), + //[ + // // Needs to be MEF driven + + // // Support a specialized requests dedicated to task-list items. This way the client can ask just + // // for these, independently of other diagnostics. They can also throttle themselves to not ask if + // // the task list would not be visible. + // new(PullDiagnosticCategories.Task), + // new(PullDiagnosticCategories.EditAndContinue), + // // Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on. + // new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), + // // Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic + // // requests, allowing the former to quickly reach the user without blocking on the latter. In a + // // similar vein, compiler diagnostics are explicitly distinct from analyzer-diagnostics, allowing + // // the former to appear as soon as possible as they are much more critical for the user and should + // // not be delayed by a slow analyzer. + // new(PullDiagnosticCategories.DocumentCompilerSyntax), + // new(PullDiagnosticCategories.DocumentCompilerSemantic), + // new(PullDiagnosticCategories.DocumentAnalyzerSyntax), + // new(PullDiagnosticCategories.DocumentAnalyzerSemantic), + //], BuildOnlyDiagnosticIds = _buildOnlyDiagnostics .SelectMany(lazy => lazy.Metadata.BuildOnlyDiagnostics) .Distinct() diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs index bc97619a79475..92e43a3d0e51a 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs @@ -13,4 +13,5 @@ internal enum DiagnosticKind CompilerSemantic = 2, AnalyzerSyntax = 3, AnalyzerSemantic = 4, + EditAndContinue = 5, } diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index f06b4057c313c..0d983d125edbc 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -50,6 +50,7 @@ + diff --git a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs index 4f6b49fea36b6..deb7877c1b234 100644 --- a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs +++ b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_OpenDocument.cs @@ -21,7 +21,7 @@ private sealed class OpenDocumentSource(Document document) : AbstractDocumentDia public override bool IsLiveSource() => true; - public override async Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { var designTimeDocument = Document; var designTimeSolution = designTimeDocument.Project.Solution; diff --git a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs index d74ca12db6421..b84fee77d2030 100644 --- a/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs +++ b/src/Features/LanguageServer/Protocol/Features/EditAndContinue/EditAndContinueDiagnosticSource_Workspace.cs @@ -22,7 +22,7 @@ private sealed class ProjectSource(Project project, ImmutableArray true; - public override Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + public override Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) => Task.FromResult(diagnostics); } @@ -31,7 +31,7 @@ private sealed class ClosedDocumentSource(TextDocument document, ImmutableArray< public override bool IsLiveSource() => true; - public override Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + public override Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) => Task.FromResult(diagnostics); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 755e5d771299d..e0f1e9de55a38 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -295,7 +294,7 @@ private async Task ComputeAndReportCurrentDiagnosticsAsync( CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var result); - var diagnostics = await diagnosticSource.GetDiagnosticsAsync(DiagnosticAnalyzerService, context, cancellationToken).ConfigureAwait(false); + var diagnostics = await diagnosticSource.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); // If we can't get a text document identifier we can't report diagnostics for this source. // This can happen for 'fake' projects (e.g. used for TS script blocks). diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..1eedb04f10b46 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs @@ -0,0 +1,207 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.CodeAnalysis.TaskList; +using Roslyn.Utilities; + + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractWorkspaceDiagnosticSourceProvider( + IDiagnosticAnalyzerService diagnosticAnalyzerService, + IGlobalOptionService globalOptions, + ImmutableArray sourceNames) + : IDiagnosticSourceProvider +{ + public bool IsDocument => false; + public ImmutableArray SourceNames => sourceNames; + + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace + // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for + // document-diagnostics instead. + if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) + return []; + + if (sourceName == PullDiagnosticCategories.Task) + return GetTaskListDiagnosticSources(context, globalOptions); + + if (sourceName == PullDiagnosticCategories.EditAndContinue) + return await EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken).ConfigureAwait(false); + + // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). + if (sourceName == PullDiagnosticCategories.WorkspaceDocumentsAndProject) + return await GetDiagnosticSourcesAsync(context, globalOptions, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + + // if it's a category we don't recognize, return nothing. + return []; + } + + private static ImmutableArray GetTaskListDiagnosticSources( + RequestContext context, IGlobalOptionService globalOptions) + { + Contract.ThrowIfNull(context.Solution); + + // Only compute task list items for closed files if the option is on for it. + var taskListEnabled = globalOptions.GetTaskListOptions().ComputeForClosedFiles; + if (!taskListEnabled) + return []; + + using var _ = ArrayBuilder.GetInstance(out var result); + + foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) + { + foreach (var document in project.Documents) + { + if (!ShouldSkipDocument(context, document)) + result.Add(new TaskListDiagnosticSource(document, globalOptions)); + } + } + + return result.ToImmutableAndClear(); + } + + private static IEnumerable GetProjectsInPriorityOrder( + Solution solution, ImmutableArray supportedLanguages) + { + return GetProjectsInPriorityOrderWorker(solution) + .WhereNotNull() + .Distinct() + .Where(p => supportedLanguages.Contains(p.Language)); + + static IEnumerable GetProjectsInPriorityOrderWorker(Solution solution) + { + var documentTrackingService = solution.Services.GetRequiredService(); + + // Collect all the documents from the solution in the order we'd like to get diagnostics for. This will + // prioritize the files from currently active projects, but then also include all other docs in all projects + // (depending on current FSA settings). + + var activeDocument = documentTrackingService.GetActiveDocument(solution); + var visibleDocuments = documentTrackingService.GetVisibleDocuments(solution); + + yield return activeDocument?.Project; + foreach (var doc in visibleDocuments) + yield return doc.Project; + + foreach (var project in solution.Projects) + yield return project; + } + } + + private static bool ShouldSkipDocument(RequestContext context, TextDocument document) + { + // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). + // Each handler treats those as separate worlds that they are responsible for. + if (context.IsTracking(document.GetURI())) + { + context.TraceInformation($"Skipping tracked document: {document.GetURI()}"); + return true; + } + + // Do not attempt to get workspace diagnostics for Razor files, Razor will directly ask us for document diagnostics + // for any razor file they are interested in. + return document.IsRazorDocument(); + } + + /// + /// There are three potential sources for reporting workspace diagnostics: + /// + /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest + /// project snapshot and return up-to-date diagnostics computed from this analysis. + /// + /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly + /// triggered code analysis execution on either the current or a prior project snapshot, we return + /// diagnostics from this execution. These diagnostics may be stale with respect to the current + /// project snapshot, but they match user's intent of not enabling continuous background analysis + /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on + /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. + /// + /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. + /// + /// If full solution analysis is disabled AND code analysis was never executed for the given project, + /// we have no workspace diagnostics to report and bail out. + /// + public static async ValueTask> GetDiagnosticSourcesAsync( + RequestContext context, IGlobalOptionService globalOptions, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(context.Solution); + + using var _ = ArrayBuilder.GetInstance(out var result); + + var solution = context.Solution; + var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; + var codeAnalysisService = solution.Services.GetRequiredService(); + + foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) + await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + + return result.ToImmutableAndClear(); + + async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); + if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) + return; + + Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled + ? ShouldIncludeAnalyzer : null; + + AddDocumentSources(project.Documents); + AddDocumentSources(project.AdditionalDocuments); + + // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. + if (enableDiagnosticsInSourceGeneratedFiles) + { + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + AddDocumentSources(sourceGeneratedDocuments); + } + + // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. + AddProjectSource(); + + return; + + void AddDocumentSources(IEnumerable documents) + { + foreach (var document in documents) + { + if (!ShouldSkipDocument(context, document)) + { + // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. + var documentDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); + result.Add(documentDiagnosticSource); + } + } + } + + void AddProjectSource() + { + var projectDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); + result.Add(projectDiagnosticSource); + } + + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) + => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + } + } +} + diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index f3911586aad23..c0872bae69c2d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -3,21 +3,13 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Collections; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.TaskList; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -27,6 +19,7 @@ internal abstract class AbstractWorkspacePullDiagnosticsHandler /// Flag that represents whether the LSP view of the world has changed. @@ -40,9 +33,11 @@ protected AbstractWorkspacePullDiagnosticsHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService diagnosticAnalyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) : base(diagnosticAnalyzerService, diagnosticRefresher, globalOptions) { + _diagnosticSourceManager = diagnosticSourceManager; _workspaceManager = workspaceManager; _workspaceRegistrationService = registrationService; @@ -56,32 +51,16 @@ public void Dispose() _workspaceRegistrationService.LspSolutionChanged -= OnLspSolutionChanged; } - protected override async ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) { - // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace - // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for - // document-diagnostics instead. - if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) - return []; - - Contract.ThrowIfNull(context.Solution); - - var category = GetDiagnosticCategory(diagnosticsParams); - - // TODO: Implement as extensibility point. https://github.com/dotnet/roslyn/issues/72896 - - if (category == PullDiagnosticCategories.Task) - return GetTaskListDiagnosticSources(context, GlobalOptions); - - if (category == PullDiagnosticCategories.EditAndContinue) - return await EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution, document => context.IsTracking(document.GetURI()), cancellationToken).ConfigureAwait(false); - - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - if (category == null || category == PullDiagnosticCategories.WorkspaceDocumentsAndProject) - return await GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken).ConfigureAwait(false); - - // if it's a category we don't recognize, return nothing. - return []; + if (GetDiagnosticCategory(diagnosticsParams) is string sourceName) + { + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, false, cancellationToken); + } + else + { + return new([]); + } } private void OnLspSolutionChanged(object? sender, WorkspaceChangeEventArgs e) @@ -115,159 +94,6 @@ protected override async Task WaitForChangesAsync(RequestContext context, Cancel return; } - private static ImmutableArray GetTaskListDiagnosticSources( - RequestContext context, IGlobalOptionService globalOptions) - { - Contract.ThrowIfNull(context.Solution); - - // Only compute task list items for closed files if the option is on for it. - var taskListEnabled = globalOptions.GetTaskListOptions().ComputeForClosedFiles; - if (!taskListEnabled) - return []; - - using var _ = ArrayBuilder.GetInstance(out var result); - - foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) - { - foreach (var document in project.Documents) - { - if (!ShouldSkipDocument(context, document)) - result.Add(new TaskListDiagnosticSource(document, globalOptions)); - } - } - - return result.ToImmutableAndClear(); - } - - /// - /// There are three potential sources for reporting workspace diagnostics: - /// - /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest - /// project snapshot and return up-to-date diagnostics computed from this analysis. - /// - /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly - /// triggered code analysis execution on either the current or a prior project snapshot, we return - /// diagnostics from this execution. These diagnostics may be stale with respect to the current - /// project snapshot, but they match user's intent of not enabling continuous background analysis - /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on - /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. - /// - /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. - /// - /// If full solution analysis is disabled AND code analysis was never executed for the given project, - /// we have no workspace diagnostics to report and bail out. - /// - public static async ValueTask> GetDiagnosticSourcesAsync( - RequestContext context, IGlobalOptionService globalOptions, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(context.Solution); - - using var _ = ArrayBuilder.GetInstance(out var result); - - var solution = context.Solution; - var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; - var codeAnalysisService = solution.Services.GetRequiredService(); - - foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) - await AddDocumentsAndProjectAsync(project, cancellationToken).ConfigureAwait(false); - - return result.ToImmutableAndClear(); - - async Task AddDocumentsAndProjectAsync(Project project, CancellationToken cancellationToken) - { - var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); - if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) - return; - - Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled - ? ShouldIncludeAnalyzer : null; - - AddDocumentSources(project.Documents); - AddDocumentSources(project.AdditionalDocuments); - - // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. - if (enableDiagnosticsInSourceGeneratedFiles) - { - var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - AddDocumentSources(sourceGeneratedDocuments); - } - - // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. - AddProjectSource(); - - return; - - void AddDocumentSources(IEnumerable documents) - { - foreach (var document in documents) - { - if (!ShouldSkipDocument(context, document)) - { - // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. - var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, shouldIncludeAnalyzer) - : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); - result.Add(documentDiagnosticSource); - } - } - } - - void AddProjectSource() - { - var projectDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, shouldIncludeAnalyzer) - : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); - result.Add(projectDiagnosticSource); - } - - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) - => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; - } - } - - private static IEnumerable GetProjectsInPriorityOrder( - Solution solution, ImmutableArray supportedLanguages) - { - return GetProjectsInPriorityOrderWorker(solution) - .WhereNotNull() - .Distinct() - .Where(p => supportedLanguages.Contains(p.Language)); - - static IEnumerable GetProjectsInPriorityOrderWorker(Solution solution) - { - var documentTrackingService = solution.Services.GetRequiredService(); - - // Collect all the documents from the solution in the order we'd like to get diagnostics for. This will - // prioritize the files from currently active projects, but then also include all other docs in all projects - // (depending on current FSA settings). - - var activeDocument = documentTrackingService.GetActiveDocument(solution); - var visibleDocuments = documentTrackingService.GetVisibleDocuments(solution); - - yield return activeDocument?.Project; - foreach (var doc in visibleDocuments) - yield return doc.Project; - - foreach (var project in solution.Projects) - yield return project; - } - } - - private static bool ShouldSkipDocument(RequestContext context, TextDocument document) - { - // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). - // Each handler treats those as separate worlds that they are responsible for. - if (context.IsTracking(document.GetURI())) - { - context.TraceInformation($"Skipping tracked document: {document.GetURI()}"); - return true; - } - - // Do not attempt to get workspace diagnostics for Razor files, Razor will directly ask us for document diagnostics - // for any razor file they are interested in. - return document.IsRazorDocument(); - } - internal abstract TestAccessor GetTestAccessor(); internal readonly struct TestAccessor(AbstractWorkspacePullDiagnosticsHandler handler) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs index 0de186f4e9ac1..f88810864a7bb 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs @@ -18,7 +18,7 @@ internal abstract class AbstractDocumentDiagnosticSource(TDocument do public abstract bool IsLiveSource(); public abstract Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); + RequestContext context, CancellationToken cancellationToken); public ProjectOrDocumentId GetId() => new(Document.Id); public Project GetProject() => Document.Project; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs index d34ef1d906826..b2efe993483c6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs @@ -16,14 +16,14 @@ internal abstract class AbstractProjectDiagnosticSource(Project project) { protected Project Project => project; - public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(project, shouldIncludeAnalyzer); + public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(project, diagnosticAnalyzerService, shouldIncludeAnalyzer); public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(project, codeAnalysisService); public abstract bool IsLiveSource(); - public abstract Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); + public abstract Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken); public ProjectOrDocumentId GetId() => new(Project.Id); public Project GetProject() => Project; @@ -33,7 +33,8 @@ public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(P : null; public string ToDisplayString() => Project.Name; - private sealed class FullSolutionAnalysisDiagnosticSource(Project project, Func? shouldIncludeAnalyzer) : AbstractProjectDiagnosticSource(project) + private sealed class FullSolutionAnalysisDiagnosticSource(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource(project) { /// /// This is a normal project source that represents live/fresh diagnostics that should supersede everything else. @@ -42,7 +43,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { @@ -55,7 +55,8 @@ public override async Task> GetDiagnosticsAsync( } } - private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) : AbstractProjectDiagnosticSource(project) + private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) + : AbstractProjectDiagnosticSource(project) { /// /// This source provides the results of the *last* explicitly kicked off "run code analysis" command from the @@ -66,7 +67,6 @@ public override bool IsLiveSource() => false; public override Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 4cc153fcc9739..07f393928e27c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -12,13 +12,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDocumentDiagnosticSource(TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(document, shouldIncludeAnalyzer); + public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(document, diagnosticAnalyzerService, shouldIncludeAnalyzer); public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDiagnostics(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(document, codeAnalysisService); - private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { /// @@ -28,7 +28,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { @@ -64,7 +63,6 @@ public override bool IsLiveSource() => false; public override Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs new file mode 100644 index 0000000000000..664bd043fa3af --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +{ + [Export(typeof(DiagnosticSourceManager)), Shared] + internal class DiagnosticSourceManager : IDiagnosticSourceManager + { + private readonly Lazy> _sources; + private ImmutableDictionary>? _documentSources; + private ImmutableDictionary>? _workspaceSources; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DiagnosticSourceManager([ImportMany] Lazy> sources) + { + _sources = sources; + } + + /// + public IEnumerable GetSourceNames(bool isDocument) + { + EnsureInitialized(); + return (isDocument ? _documentSources : _workspaceSources)!.Keys; + } + + /// + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) + { + EnsureInitialized(); + var providersDictionary = isDocument ? _documentSources : _workspaceSources; + if (!providersDictionary!.TryGetValue(sourceName, out var providers)) + { + return []; + } + + using var _ = ArrayBuilder.GetInstance(out var builder); + foreach (var provider in providers) + { + var sources = await provider.CreateDiagnosticSourcesAsync(context, sourceName, cancellationToken).ConfigureAwait(false); + builder.AddRange(sources); + } + + return builder.ToImmutableAndClear(); + } + + private void EnsureInitialized() + { + if (_documentSources == null || _workspaceSources == null) + { + Dictionary> documentSources = new(); + Dictionary> workspaceSources = new(); + foreach (var source in _sources.Value) + { + var attribute = source.GetType().GetCustomAttributes(inherit: false).FirstOrDefault(); + if (attribute != null) + { + var scopedSources = source.IsDocument ? documentSources : workspaceSources; + foreach (var sourceName in source.SourceNames) + { + if (!scopedSources.TryGetValue(sourceName, out var sources)) + { + sources = new List(); + scopedSources[sourceName] = sources; + } + ((List)sources).Add(source); + } + } + } + + var immutableSources = documentSources.ToImmutableDictionary(entry => entry.Key, entry => entry.Value.ToImmutableArray()); + Interlocked.CompareExchange(ref _documentSources, immutableSources, null); + immutableSources = workspaceSources.ToImmutableDictionary(entry => entry.Key, entry => entry.Value.ToImmutableArray()); + Interlocked.CompareExchange(ref _workspaceSources, immutableSources, null); + } + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 54f5c6ce5deb2..5cae97d9f3229 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -11,7 +11,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, TextDocument document) + +internal sealed class DocumentDiagnosticSource(IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { public DiagnosticKind DiagnosticKind { get; } = diagnosticKind; @@ -23,14 +24,14 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + RequestContext context, CancellationToken cancellationToken) { // We call GetDiagnosticsForSpanAsync here instead of GetDiagnosticsForIdsAsync as it has faster perf // characteristics. GetDiagnosticsForIdsAsync runs analyzers against the entire compilation whereas // GetDiagnosticsForSpanAsync will only run analyzers against the request document. // Also ensure we pass in "includeSuppressedDiagnostics = true" for unnecessary suppressions to be reported. var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( - Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); // Add cached Copilot diagnostics when computing analyzer semantic diagnostics. // TODO: move to a separate diagnostic source. https://github.com/dotnet/roslyn/issues/72896 diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs new file mode 100644 index 0000000000000..2c165e3d7ca29 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; + +/// +/// Use this attribute to declare a implementation for inclusion in a MEF-based workspace. +/// +/// +/// Declares a implementation for inclusion in a MEF-based workspace. +/// +[MetadataAttribute] +[AttributeUsage(AttributeTargets.Class)] +internal class ExportDiagnosticSourceProviderAttribute() : ExportAttribute(typeof(IDiagnosticSourceProvider)) +{ +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs index 34d110e4adb4c..242be19b8d8b6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -15,7 +14,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; /// Wrapper around a source for diagnostics (e.g. a or ) /// so that we can share per file diagnostic reporting code in /// -internal interface IDiagnosticSource +internal interface IDiagnosticSource // we'll need one for XHR errors { Project GetProject(); ProjectOrDocumentId GetId(); @@ -33,7 +32,6 @@ internal interface IDiagnosticSource bool IsLiveSource(); Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs new file mode 100644 index 0000000000000..3246e5cc9153c --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources +{ + /// + /// Manages the diagnostic sources that provide diagnostics for the language server. + /// + internal interface IDiagnosticSourceManager + { + /// + /// Returns the names of all the sources that provide diagnostics for the given . + /// + /// True for document sources and false for workspace sources. + IEnumerable GetSourceNames(bool isDocument); + + /// + /// Creates the diagnostic sources for the given and . + /// + /// The context. + /// Source name. + /// True for document sources and false for workspace sources. + /// The cancellation token. + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..8c68d8b82094e --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +/// +/// Provides diagnostic sources. +/// +internal interface IDiagnosticSourceProvider +{ + /// + /// True if this provider is for documents, false if it is for workspace. + /// + bool IsDocument { get; } + + /// + /// Source names that this provider can provide. + /// + ImmutableArray SourceNames { get; } + + /// + /// Creates the diagnostic sources for the given . + /// + /// The context. + /// Source name. + /// The cancellation token. + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken); +} + diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs index 8ffd2d9b75fb9..d370f15240a60 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class NonLocalDocumentDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) +internal sealed class NonLocalDocumentDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) : AbstractDocumentDiagnosticSource(document) { private readonly Func? _shouldIncludeAnalyzer = shouldIncludeAnalyzer; @@ -19,7 +19,6 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs index df8a3491ccdcc..fe6ebc182c6f9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/TaskListDiagnosticSource.cs @@ -33,7 +33,7 @@ public override bool IsLiveSource() => true; public override async Task> GetDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, CancellationToken cancellationToken) + RequestContext context, CancellationToken cancellationToken) { var service = this.Document.GetLanguageService(); if (service == null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..e13833fe0dbe5 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.SolutionCrawler; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DocumentDiagnosticSourceProvider( + [Import] IGlobalOptionService globalOptions, + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : IDiagnosticSourceProvider +{ + private static readonly ImmutableArray sourceNames = + [ + PullDiagnosticCategories.Task, + PullDiagnosticCategories.DocumentCompilerSyntax, + PullDiagnosticCategories.DocumentCompilerSemantic, + PullDiagnosticCategories.DocumentAnalyzerSyntax, + PullDiagnosticCategories.DocumentAnalyzerSemantic + ]; + + public bool IsDocument => true; + public ImmutableArray SourceNames => sourceNames; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + if (sourceName == PullDiagnosticCategories.Task) + return new(GetDiagnosticSources(diagnosticAnalyzerService, diagnosticKind: default, nonLocalDocumentDiagnostics: false, taskList: true, context, globalOptions)); + + if (sourceName == PullDiagnosticCategories.EditAndContinue) + { + if (GetEditAndContinueDiagnosticSource(context) is IDiagnosticSource source) + { + return new([source]); + } + + return new([]); + } + + var diagnosticKind = sourceName switch + { + PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, + PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, + PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, + PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, + //// if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). + //null => DiagnosticKind.All, // !!!VS Code does not request any diag kind!!! + // // if it's a category we don't recognize, return nothing. + _ => (DiagnosticKind?)null, + }; + + if (diagnosticKind is null) + return new([]); + + return new(GetDiagnosticSources(diagnosticAnalyzerService, diagnosticKind.Value, nonLocalDocumentDiagnostics: false, taskList: false, context, globalOptions)); + } + + internal static IDiagnosticSource? GetEditAndContinueDiagnosticSource(RequestContext context) + => context.GetTrackedDocument() is { } document ? EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document) : null; + + internal static ImmutableArray GetDiagnosticSources( + IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, bool nonLocalDocumentDiagnostics, bool taskList, RequestContext context, IGlobalOptionService globalOptions) + { + // For the single document case, that is the only doc we want to process. + // + // Note: context.Document may be null in the case where the client is asking about a document that we have + // since removed from the workspace. In this case, we don't really have anything to process. + // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. + // + // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each + // handler treats those as separate worlds that they are responsible for. + var textDocument = context.TextDocument; + if (textDocument is null) + { + context.TraceInformation("Ignoring diagnostics request because no text document was provided"); + return []; + } + + var document = textDocument as Document; + if (taskList && document is null) + { + context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); + return []; + } + + if (!context.IsTracking(textDocument.GetURI())) + { + context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); + return []; + } + + if (nonLocalDocumentDiagnostics) + return GetNonLocalDiagnosticSources(); + + return taskList + ? [new TaskListDiagnosticSource(document!, globalOptions)] + : [new DocumentDiagnosticSource(diagnosticAnalyzerService, diagnosticKind, textDocument)/*, Xaml source might go here; ???why doc while it should we workspace???*/]; + + ImmutableArray GetNonLocalDiagnosticSources() + { + Debug.Assert(!taskList); + + // This code path is currently only invoked from the public LSP handler, which always uses 'DiagnosticKind.All' + Debug.Assert(diagnosticKind == DiagnosticKind.All); + + // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. + if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + return []; + + return [new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]; + + // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index eace41f6259c5..401bbcb314cd3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -3,128 +3,90 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -[Method(VSInternalMethods.DocumentPullDiagnosticName)] -internal partial class DocumentPullDiagnosticHandler - : AbstractDocumentPullDiagnosticHandler +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { - public DocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, globalOptions) + [Method(VSInternalMethods.DocumentPullDiagnosticName)] + internal partial class DocumentPullDiagnosticHandler + : AbstractDocumentPullDiagnosticHandler { - } - - protected override string? GetDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.QueryingDiagnosticKind?.Value; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; + public DocumentPullDiagnosticHandler( + IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : base(analyzerService, diagnosticRefresher, globalOptions) + { + _diagnosticSourceManager = diagnosticSourceManager; + } - public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.TextDocument; + protected override string? GetDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; + + public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.TextDocument; + + protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) + => [ + new VSInternalDiagnosticReport + { + Diagnostics = diagnostics, + ResultId = resultId, + Identifier = DocumentDiagnosticIdentifier, + // Mark these diagnostics as superseding any diagnostics for the same document from the + // WorkspacePullDiagnosticHandler. We are always getting completely accurate and up to date diagnostic + // values for a particular file, so our results should always be preferred over the workspace-pull + // values which are cached and may be out of date. + Supersedes = WorkspaceDiagnosticIdentifier, + } + ]; + + protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); + + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) + { + report = CreateReport(identifier, diagnostics: null, resultId); + return true; + } - protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - => [ - new VSInternalDiagnosticReport + protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) + { + if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) { - Diagnostics = diagnostics, - ResultId = resultId, - Identifier = DocumentDiagnosticIdentifier, - // Mark these diagnostics as superseding any diagnostics for the same document from the - // WorkspacePullDiagnosticHandler. We are always getting completely accurate and up to date diagnostic - // values for a particular file, so our results should always be preferred over the workspace-pull - // values which are cached and may be out of date. - Supersedes = WorkspaceDiagnosticIdentifier, + return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); } - ]; - protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); + // The client didn't provide us with a previous result to look for, so we can't lookup anything. + return null; + } - protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) - { - report = CreateReport(identifier, diagnostics: null, resultId); - return true; - } + protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) + => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); - protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) - { - if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync( + VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) { - return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); + if (diagnosticsParams.QueryingDiagnosticKind?.Value is string sourceName) + { + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); + } + else + { + return new([]); + } } - // The client didn't provide us with a previous result to look for, so we can't lookup anything. - return null; - } - - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) - => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); - - protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) - => progress.GetFlattenedValues(); - - protected override ValueTask> GetOrderedDiagnosticSourcesAsync( - VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) - => new(GetDiagnosticSource(diagnosticsParams, context) is { } diagnosticSource ? [diagnosticSource] : []); - - private IDiagnosticSource? GetDiagnosticSource(VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context) - { - var category = diagnosticsParams.QueryingDiagnosticKind?.Value; - - // TODO: Implement as extensibility point. https://github.com/dotnet/roslyn/issues/72896 - - if (category == PullDiagnosticCategories.Task) - return context.GetTrackedDocument() is { } document ? new TaskListDiagnosticSource(document, GlobalOptions) : null; - - if (category == PullDiagnosticCategories.EditAndContinue) - return GetEditAndContinueDiagnosticSource(context); - - var diagnosticKind = category switch + protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) { - PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, - PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, - PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, - PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - null => DiagnosticKind.All, - // if it's a category we don't recognize, return nothing. - _ => (DiagnosticKind?)null, - }; - - if (diagnosticKind is null) - return null; - - return GetDiagnosticSource(diagnosticKind.Value, context); - } - - internal static IDiagnosticSource? GetEditAndContinueDiagnosticSource(RequestContext context) - => context.GetTrackedDocument() is { } document ? EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document) : null; - - internal static IDiagnosticSource? GetDiagnosticSource(DiagnosticKind diagnosticKind, RequestContext context) - => context.GetTrackedDocument() is { } textDocument ? new DocumentDiagnosticSource(diagnosticKind, textDocument) : null; - - internal static IDiagnosticSource? GetNonLocalDiagnosticSource(RequestContext context, IGlobalOptionService globalOptions) - { - var textDocument = context.GetTrackedDocument(); - if (textDocument == null) - return null; - - // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) - return null; - - // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - return new NonLocalDocumentDiagnosticSource(textDocument, shouldIncludeAnalyzer: static analyzer => !analyzer.IsCompilerAnalyzer()); + return progress.GetFlattenedValues(); + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs index d731fe70276cd..07a6cfcaabc17 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -14,6 +15,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory { private readonly IDiagnosticAnalyzerService _analyzerService; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly IDiagnosticsRefresher _diagnosticsRefresher; private readonly IGlobalOptionService _globalOptions; @@ -21,14 +23,16 @@ internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public DocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) { _analyzerService = analyzerService; + _diagnosticSourceManager = diagnosticSourceManager; _diagnosticsRefresher = diagnosticsRefresher; _globalOptions = globalOptions; } public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) - => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticsRefresher, _globalOptions); + => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticSourceManager, _diagnosticsRefresher, _globalOptions); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..1ef1c07135337 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PublicDocumentDiagnosticSourceProvider( + [Import] IGlobalOptionService globalOptions, + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : IDiagnosticSourceProvider +{ + public const string NonLocalSource = "nonLocal_B69807DB-28FB-4846-884A-1152E54C8B62"; + private static readonly ImmutableArray sourceNames = [NonLocalSource]; + + public bool IsDocument => true; + public ImmutableArray SourceNames => sourceNames; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + var nonLocalDocumentDiagnostics = sourceName == NonLocalSource; + var result = DocumentDiagnosticSourceProvider.GetDiagnosticSources(diagnosticAnalyzerService, DiagnosticKind.All, nonLocalDocumentDiagnostics, taskList: false, context, globalOptions); + return new(result); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs index e369e4180ed43..3ae460c780e84 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs @@ -5,10 +5,9 @@ using System; using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; -using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -19,6 +18,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFactory { private readonly IDiagnosticAnalyzerService _analyzerService; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly IDiagnosticsRefresher _diagnosticRefresher; private readonly IGlobalOptionService _globalOptions; @@ -26,10 +26,12 @@ internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFa [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public PublicDocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) { _analyzerService = analyzerService; + _diagnosticSourceManager = diagnosticSourceManager; _diagnosticRefresher = diagnosticRefresher; _globalOptions = globalOptions; } @@ -37,6 +39,6 @@ public PublicDocumentPullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var clientLanguageServerManager = lspServices.GetRequiredService(); - return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticRefresher, _globalOptions); + return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticSourceManager, _diagnosticRefresher, _globalOptions); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 400087cd41ef7..15e6062a31754 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -8,34 +8,35 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; -using DocumentDiagnosticReport = SumType; - // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed // by n DocumentDiagnosticPartialResult literals. // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic using DocumentDiagnosticPartialReport = SumType; +using DocumentDiagnosticReport = SumType; [Method(Methods.TextDocumentDiagnosticName)] internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler { private readonly string _nonLocalDiagnosticsSourceRegistrationId; private readonly IClientLanguageServerManager _clientLanguageServerManager; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; public PublicDocumentPullDiagnosticsHandler( IClientLanguageServerManager clientLanguageServerManager, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : base(analyzerService, diagnosticsRefresher, globalOptions) { _nonLocalDiagnosticsSourceRegistrationId = Guid.NewGuid().ToString(); + _diagnosticSourceManager = diagnosticSourceManager; _clientLanguageServerManager = clientLanguageServerManager; } @@ -94,14 +95,10 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi protected override ValueTask> GetOrderedDiagnosticSourcesAsync(DocumentDiagnosticParams diagnosticParams, RequestContext context, CancellationToken cancellationToken) { - var nonLocalDocumentDiagnostics = diagnosticParams.Identifier == DocumentNonLocalDiagnosticIdentifier.ToString(); - - // Task list items are not reported through the public LSP diagnostic API. - var source = nonLocalDocumentDiagnostics - ? DocumentPullDiagnosticHandler.GetNonLocalDiagnosticSource(context, GlobalOptions) - : DocumentPullDiagnosticHandler.GetDiagnosticSource(DiagnosticKind.All, context); - - return new(source != null ? [source] : []); + var sourceName = diagnosticParams.Identifier == DocumentNonLocalDiagnosticIdentifier.ToString() + ? PublicDocumentDiagnosticSourceProvider.NonLocalSource + : string.Empty; + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); } protected override ImmutableArray? GetPreviousResults(DocumentDiagnosticParams diagnosticsParams) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index 940723c0e652a..bc4fa71d8804e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.SolutionCrawler; @@ -28,26 +29,24 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ // TODO: Hookup an option changed handler for changes to BackgroundAnalysisScopeOption // to dynamically register/unregister the non-local document diagnostic source. + var sources = _diagnosticSourceManager.GetSourceNames(isDocument: true); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams() { - Registrations = - [ - new Registration - { - Id = _nonLocalDiagnosticsSourceRegistrationId, - Method = Methods.TextDocumentDiagnosticName, - RegisterOptions = new DiagnosticRegistrationOptions - { - Identifier = DocumentNonLocalDiagnosticIdentifier.ToString() - } - } - ] + Registrations = sources.Select(FromSourceName).ToArray() }, cancellationToken).ConfigureAwait(false); } + Registration FromSourceName(string sourceName) + => new() + { + Id = sourceName, + Method = Methods.TextDocumentDiagnosticName, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } + }; + bool IsFsaEnabled() { foreach (var language in context.SupportedLanguages) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..d806d8e235ea0 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PublicWorkspaceDiagnosticSourceProvider( + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, + [Import] IGlobalOptionService globalOptions) + : AbstractWorkspaceDiagnosticSourceProvider(diagnosticAnalyzerService, globalOptions, [""]) +{ +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs index 90970a521fe53..9e11c966f21c4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -16,12 +17,14 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions); + var clientLanguageServerManager = lspServices.GetRequiredService(); + return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, clientLanguageServerManager, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 6c2beb5517041..6c72098ffaf41 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -7,6 +7,7 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -19,15 +20,21 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using WorkspaceDiagnosticPartialReport = SumType; [Method(Methods.WorkspaceDiagnosticName)] -internal sealed class PublicWorkspacePullDiagnosticsHandler( +internal sealed partial class PublicWorkspacePullDiagnosticsHandler: AbstractWorkspacePullDiagnosticsHandler, IDisposable +{ + private readonly IClientLanguageServerManager _clientLanguageServerManager; + public PublicWorkspacePullDiagnosticsHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, + IClientLanguageServerManager clientLanguageServerManager, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) - : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticRefresher, globalOptions), IDisposable -{ + : base(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticRefresher, globalOptions) + { + _clientLanguageServerManager = clientLanguageServerManager; + } /// /// Public API doesn't support categories (yet). diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs new file mode 100644 index 0000000000000..4ee7886ed524e --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public +{ + internal sealed partial class PublicWorkspacePullDiagnosticsHandler : IOnInitialized + { + public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) + { + var sources = _diagnosticSourceManager.GetSourceNames(isDocument: false); + await _clientLanguageServerManager.SendRequestAsync( + methodName: Methods.ClientRegisterCapabilityName, + @params: new RegistrationParams() + { + Registrations = sources.Select(FromSourceName).ToArray() + }, + cancellationToken).ConfigureAwait(false); + + Registration FromSourceName(string sourceName) + => new() + { + Id = sourceName, + Method = Methods.WorkspaceDiagnosticName, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } + }; + } + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..ca21d6c822fee --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceDiagnosticSourceProvider( + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, + [Import] IGlobalOptionService globalOptions) + : AbstractWorkspaceDiagnosticSourceProvider(diagnosticAnalyzerService, globalOptions, + [PullDiagnosticCategories.EditAndContinue, PullDiagnosticCategories.WorkspaceDocumentsAndProject]) +{ +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 66893cacf32fb..63e9e83e53495 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -17,52 +18,53 @@ internal sealed partial class WorkspacePullDiagnosticHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions) + workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { protected override string? GetDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) => diagnosticsParams.QueryingDiagnosticKind?.Value; - protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - => [ - new VSInternalWorkspaceDiagnosticReport - { - TextDocument = identifier, - Diagnostics = diagnostics, - ResultId = resultId, - // Mark these diagnostics as having come from us. They will be superseded by any diagnostics for the - // same file produced by the DocumentPullDiagnosticHandler. - Identifier = WorkspaceDiagnosticIdentifier, - } - ]; +protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) + => [ + new VSInternalWorkspaceDiagnosticReport + { + TextDocument = identifier, + Diagnostics = diagnostics, + ResultId = resultId, + // Mark these diagnostics as having come from us. They will be superseded by any diagnostics for the + // same file produced by the DocumentPullDiagnosticHandler. + Identifier = WorkspaceDiagnosticIdentifier, + } + ]; - protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); +protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); - protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) - { - // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of - // these and we can save a lot of memory not serializing/deserializing all of this. - report = null; - return false; - } - - protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); +protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) +{ + // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of + // these and we can save a lot of memory not serializing/deserializing all of this. + report = null; + return false; +} - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) - { - // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics - // produced by document diagnostics. - return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); - } +protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); - protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) - { - return progress.GetFlattenedValues(); - } +protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) +{ + // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics + // produced by document diagnostics. + return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); +} - internal override TestAccessor GetTestAccessor() => new(this); +protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) +{ + return progress.GetFlattenedValues(); } + +internal override TestAccessor GetTestAccessor() => new(this); +} \ No newline at end of file diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs index b928cf51f3910..7c13f4d46b74a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs @@ -6,6 +6,7 @@ using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -16,12 +17,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal class WorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticsRefresher, globalOptions); + return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs new file mode 100644 index 0000000000000..9e55d78ba20e3 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +{ + internal interface IHotReloadDiagnosticService + { + /// + /// Update the diagnostics for the given group name. + /// + /// The diagnostics. + /// The group name. + void UpdateDiagnostics(IEnumerable diagnostics, string groupName); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs new file mode 100644 index 0000000000000..6b8b08db21c92 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +[Export(typeof(IHotReloadDiagnosticService)), Shared] +internal sealed class HotReloadDiagnosticService : IHotReloadDiagnosticService +{ + private readonly IHotReloadDiagnosticService? _implementation; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public HotReloadDiagnosticService([ImportMany] IEnumerable sourceProviders) + { + _implementation = sourceProviders.OfType().FirstOrDefault(); + } + + void IHotReloadDiagnosticService.UpdateDiagnostics(IEnumerable diagnostics, string groupName) + => _implementation?.UpdateDiagnostics(diagnostics, groupName); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs new file mode 100644 index 0000000000000..94cc82c6277f5 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +internal sealed class HotReloadDiagnosticSource(Project project, IDiagnosticsRefresher diagnosticsRefresher) : IDiagnosticSource +{ + Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => null; + ProjectOrDocumentId IDiagnosticSource.GetId() => new(project.Id); + Project IDiagnosticSource.GetProject() => project; + bool IDiagnosticSource.IsLiveSource() => true; + string IDiagnosticSource.ToDisplayString() => project.Name; +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..7a575e5885cd7 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +// THIS IS WRONG. Need to follow EdinAndContinue +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class HotReloadDiagnosticSourceProvider(IDiagnosticsRefresher diagnosticsRefresher) + : IDiagnosticSourceProvider + , IHotReloadDiagnosticService +{ + private const string SourceName = "HotReloadDiagnostic"; + private static readonly ImmutableArray sourceNames = [SourceName]; + + private readonly ConcurrentDictionary projectDiagnostics = new(); + + bool IDiagnosticSourceProvider.IsDocument => false; + ImmutableArray IDiagnosticSourceProvider.SourceNames => sourceNames; + + void IHotReloadDiagnosticService.UpdateDiagnostics(IEnumerable diagnostics, string sourceName) + { + // TODO: store diagnostics in projectDiagnostics + //foreach (var diagnostic in diagnostics) + //{ + //} + diagnosticsRefresher.RequestWorkspaceRefresh(); + } + + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + if (sourceName == SourceName) + { + return new(projectDiagnostics.Values.ToImmutableArray()); + } + + return new([]); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 504106533fe6b..8460dac45da33 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,15 +1,17 @@ +const Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource.SourceName = "XamlHotReloadDiagnostics" -> string! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticService +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticService.UpdateDiagnostics(System.Collections.Generic.IEnumerable! diagnostics, string! groupName) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStartAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStopAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.InitializeAsync(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.RequestDataBridgeConnectionAsync(string! connectionId, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsServiceBroker -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsServiceBroker.GetServiceBrokerAsync() -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.NotifyServiceBrokerInitialized.get -> Microsoft.CodeAnalysis.BrokeredServices.IOnServiceBrokerInitialized? -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.NotifyServiceBrokerInitialized.set -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.OnServiceBrokerInitialized(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.VisualDiagnosticsServiceBroker.VisualDiagnosticsServiceBroker() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticService +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticService.HotReloadDiagnosticService(System.Collections.Generic.IEnumerable! sourceProviders) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.HotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource.XamlHotReloadDiagnosticSource(Microsoft.CodeAnalysis.Project! project, Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry.RunningProcessEntry() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory From 58c82d8e1a94b85f01bdbd2db6f02644dcea5b09 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 14:36:09 -0700 Subject: [PATCH 059/292] Fix test --- .../Services/SolutionServiceTests.cs | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index f80e0bd521a6b..35fc855463518 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -31,7 +32,7 @@ namespace Roslyn.VisualStudio.Next.UnitTests.Remote; public class SolutionServiceTests { private static readonly TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = - FeaturesTestCompositions.Features.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); + FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess).AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); private static RemoteWorkspace CreateRemoteWorkspace() => new(FeaturesTestCompositions.RemoteHost.GetHostServices()); @@ -958,6 +959,69 @@ public async Task TestRemoteWorkspaceCachesNothingIfActiveDocumentNotSynced() objectReference2.AssertReleased(); } + [Theory, CombinatorialData] + public async Task TestRemoteWorkspaceCachesPropertyIfActiveDocumentIsSynced(bool updatePrimaryBranch) + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + // Locally the semantic model will be held + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1.AssertHeld(); + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + var remoteDocumentTrackingService = (RemoteDocumentTrackingService)remoteWorkspace.Services.GetRequiredService(); + remoteDocumentTrackingService.SetActiveDocument(document1.Id); + + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, CancellationToken.None); + + // The remote semantic model will be held as it refers to the active document. + var objectReference2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference2.AssertHeld(); + } + + [Theory, CombinatorialData] + public async Task ValidateUpdaterInformsRemoteWorkspaceOfActiveDocument(bool updatePrimaryBranch) + { + var code = @"class Test { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithFirstDocumentIsActiveAndVisible); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.Single(); + + // Locally the semantic model will be held + var objectReference1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1.AssertHeld(); + + // By creating a checksum updater, we should notify the remote workspace of the active document. + var listenerProvider = workspace.ExportProvider.GetExportedValue(); + var checksumUpdater = new SolutionChecksumUpdater(workspace, listenerProvider, CancellationToken.None); + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, CancellationToken.None); + + var waiter = listenerProvider.GetWaiter(FeatureAttribute.SolutionChecksumUpdater); + await waiter.ExpeditedWaitAsync(); + + // The remote semantic model will be held as it refers to the active document. + var objectReference2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference2.AssertHeld(); + } + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) { using var workspace = TestWorkspace.CreateCSharp(code); From 837bc32add3c54a054e3f5a42ce161c6cef7dc3a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 15:09:20 -0700 Subject: [PATCH 060/292] Add test --- ...ActiveAndVisibleDocumentTrackingService.cs | 2 +- .../TestDocumentTrackingService.cs | 36 +++++++++ .../NavigateTo/AbstractNavigateToTests.cs | 1 + .../TestDocumentTrackingService.cs | 45 ----------- .../Services/SolutionServiceTests.cs | 80 ++++++++++++++++++- 5 files changed, 117 insertions(+), 47 deletions(-) rename src/EditorFeatures/TestUtilities/{Workspaces => DocumentTracking}/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs (97%) create mode 100644 src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs delete mode 100644 src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs diff --git a/src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/DocumentTracking/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs similarity index 97% rename from src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs rename to src/EditorFeatures/TestUtilities/DocumentTracking/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs index 55fc88be78ef8..4f8ccd9d7cdc6 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/DocumentTracking/FirstDocumentIsActiveAndVisibleDocumentTrackingService.cs @@ -10,7 +10,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Test.Utilities; +namespace Microsoft.CodeAnalysis.Editor.Test; internal sealed class FirstDocumentIsActiveAndVisibleDocumentTrackingService : IDocumentTrackingService { diff --git a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs new file mode 100644 index 0000000000000..f61704e592cb8 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Editor.Test; + +[ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] +internal sealed class TestDocumentTrackingService : IDocumentTrackingService +{ + private DocumentId? _activeDocumentId; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestDocumentTrackingService() + { + } + + public event EventHandler? ActiveDocumentChanged; + + public void SetActiveDocument(DocumentId? newActiveDocumentId) + { + _activeDocumentId = newActiveDocumentId; + ActiveDocumentChanged?.Invoke(this, newActiveDocumentId); + } + + public DocumentId? TryGetActiveDocument() + => _activeDocumentId; + + public ImmutableArray GetVisibleDocuments() + => _activeDocumentId != null ? ImmutableArray.Create(_activeDocumentId) : ImmutableArray.Empty; +} diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index f494a0f9f0969..e318768462e40 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Editor.Implementation.NavigateTo; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; diff --git a/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs deleted file mode 100644 index 91f7f5d0df0ec..0000000000000 --- a/src/EditorFeatures/TestUtilities/SolutionCrawler/TestDocumentTrackingService.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.Editor.Test -{ - [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal sealed class TestDocumentTrackingService : IDocumentTrackingService - { - private DocumentId? _activeDocumentId; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestDocumentTrackingService() - { - } - - public bool SupportsDocumentTracking => true; - - public event EventHandler? ActiveDocumentChanged; - - public event EventHandler NonRoslynBufferTextChanged - { - add { } - remove { } - } - - public void SetActiveDocument(DocumentId? newActiveDocumentId) - { - _activeDocumentId = newActiveDocumentId; - ActiveDocumentChanged?.Invoke(this, newActiveDocumentId); - } - - public DocumentId? TryGetActiveDocument() - => _activeDocumentId; - - public ImmutableArray GetVisibleDocuments() - => _activeDocumentId != null ? ImmutableArray.Create(_activeDocumentId) : ImmutableArray.Empty; - } -} diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 35fc855463518..93583058aead3 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; @@ -31,8 +32,9 @@ namespace Roslyn.VisualStudio.Next.UnitTests.Remote; [Trait(Traits.Feature, Traits.Features.RemoteHost)] public class SolutionServiceTests { + private static readonly TestComposition s_composition = FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess); private static readonly TestComposition s_compositionWithFirstDocumentIsActiveAndVisible = - FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess).AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); + s_composition.AddParts(typeof(FirstDocumentIsActiveAndVisibleDocumentTrackingService.Factory)); private static RemoteWorkspace CreateRemoteWorkspace() => new(FeaturesTestCompositions.RemoteHost.GetHostServices()); @@ -1022,6 +1024,82 @@ public async Task ValidateUpdaterInformsRemoteWorkspaceOfActiveDocument(bool upd objectReference2.AssertHeld(); } + [Theory, CombinatorialData] + public async Task ValidateUpdaterInformsRemoteWorkspaceOfActiveDocument_EvenAcrossActiveDocumentChanges(bool updatePrimaryBranch) + { + using var workspace = TestWorkspace.Create(""" + + + + class Program1 + { + } + + + class Program2 + { + } + + + + """, composition: s_composition.AddParts(typeof(TestDocumentTrackingService))); + using var remoteWorkspace = CreateRemoteWorkspace(); + + var solution = workspace.CurrentSolution; + + var project1 = solution.Projects.Single(); + var document1 = project1.Documents.First(); + var document2 = project1.Documents.Last(); + + // By creating a checksum updater, we should notify the remote workspace of the active document. Have it + // initially be set to the first document. + var documentTrackingService = (TestDocumentTrackingService)workspace.Services.GetRequiredService(); + documentTrackingService.SetActiveDocument(document1.Id); + + // Locally the semantic model for the first document will be held, but the second will not. + var objectReference1_step1 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + var objectReference2_step1 = ObjectReference.CreateFromFactory(() => document2.GetSemanticModelAsync().GetAwaiter().GetResult()); + objectReference1_step1.AssertHeld(); + objectReference2_step1.AssertReleased(); + + var listenerProvider = workspace.ExportProvider.GetExportedValue(); + var checksumUpdater = new SolutionChecksumUpdater(workspace, listenerProvider, CancellationToken.None); + + var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); + + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); + var syncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, CancellationToken.None); + + var waiter = listenerProvider.GetWaiter(FeatureAttribute.SolutionChecksumUpdater); + await waiter.ExpeditedWaitAsync(); + + // The remote semantic model should match the local behavior once it has been notified that the first document is active. + var oopDocumentReference1_step1 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + var oopDocumentReference2_step1 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document2.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + oopDocumentReference1_step1.AssertHeld(); + oopDocumentReference2_step1.AssertReleased(); + + // Now, change the active document to the second document. + documentTrackingService.SetActiveDocument(document2.Id); + + // And get the semantic models again. The second document should now be held, and the first released. + var objectReference1_step2 = ObjectReference.CreateFromFactory(() => document1.GetSemanticModelAsync().GetAwaiter().GetResult()); + var objectReference2_step2 = ObjectReference.CreateFromFactory(() => document2.GetSemanticModelAsync().GetAwaiter().GetResult()); + + // The second document should be held. + objectReference2_step2.AssertHeld(); + + // Ensure that the active doc change is sync'ed to oop. + await waiter.ExpeditedWaitAsync(); + + // And get the semantic models again on the oop side. The second document should now be held, and the first released. + var oopDocumentReference1_step2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document1.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + var oopDocumentReference2_step2 = ObjectReference.CreateFromFactory(() => syncedSolution.GetRequiredDocument(document2.Id).GetSemanticModelAsync().GetAwaiter().GetResult()); + + // The second document on oop should now be held. + oopDocumentReference2_step2.AssertHeld(); + } + private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) { using var workspace = TestWorkspace.CreateCSharp(code); From 5b1e9f4ce27df0bdfc28e888a9a245d39a023b5a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 15:17:01 -0700 Subject: [PATCH 061/292] Fix tests --- .../CSharpTest/NavigateTo/NavigateToSearcherTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs index 7092c2679be22..fca8d0d529356 100644 --- a/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs +++ b/src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs @@ -7,8 +7,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Test; using Microsoft.CodeAnalysis.Editor.UnitTests; -using Microsoft.CodeAnalysis.Editor.UnitTests.NavigateTo; using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.Navigation; using Microsoft.CodeAnalysis.PatternMatching; From f468e96071110c163d61c7cae23ef543c5bafe0f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 15:26:13 -0700 Subject: [PATCH 062/292] Apply suggestions from code review --- src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 53322e1d31de7..12b1fafbd9d8f 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -100,8 +100,7 @@ public void Shutdown() // Try to stop any work that is in progress. PauseWork(); - var trackingService = _workspace.Services.GetRequiredService(); - trackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; + _documentTrackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; _workspace.WorkspaceChanged -= OnWorkspaceChanged; From 302392fabf425ce156d4e5b7377f31216ca5ebd2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 15:27:56 -0700 Subject: [PATCH 063/292] Primary constructor --- .../DocumentTracking/TestDocumentTrackingService.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs index f61704e592cb8..44fe5a20d19c0 100644 --- a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs @@ -10,16 +10,12 @@ namespace Microsoft.CodeAnalysis.Editor.Test; [ExportWorkspaceService(typeof(IDocumentTrackingService), ServiceLayer.Test), Shared, PartNotDiscoverable] -internal sealed class TestDocumentTrackingService : IDocumentTrackingService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class TestDocumentTrackingService() : IDocumentTrackingService { private DocumentId? _activeDocumentId; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestDocumentTrackingService() - { - } - public event EventHandler? ActiveDocumentChanged; public void SetActiveDocument(DocumentId? newActiveDocumentId) From d71f661297fa017f5159e358e142df80783eb02b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 15:31:34 -0700 Subject: [PATCH 064/292] Simplify --- .../Solution/Solution_SemanticModelCaching.cs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs index 970ed107c0b2f..32bebdc86d8d5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution_SemanticModelCaching.cs @@ -30,17 +30,30 @@ public partial class Solution internal void OnSemanticModelObtained(DocumentId documentId, SemanticModel semanticModel) { var service = this.Services.GetRequiredService(); + var activeDocumentId = service.TryGetActiveDocument(); - if (activeDocumentId != documentId) + if (activeDocumentId is null) + { + // no active document? then clear out any caches we have. + _activeDocumentSemanticModel = null; + _activeDocumentNullableDisabledSemanticModel = null; + } + else if (activeDocumentId != documentId) + { + // We have an active document, but we just obtained the semantic model for some other doc. Nothing to do + // here, we don't want to cache this. return; - - // Ok. We just obtained the semantic model for the active document. Make a strong reference to it so that - // other features that wake up for this active document are sure to be able to reuse the same one. -#pragma warning disable RSEXPERIMENTAL001 // sym-shipped usage of experimental API - if (semanticModel.NullableAnalysisIsDisabled) - _activeDocumentNullableDisabledSemanticModel = semanticModel; + } else - _activeDocumentSemanticModel = semanticModel; + { + // Ok. We just obtained the semantic model for the active document. Make a strong reference to it so that + // other features that wake up for this active document are sure to be able to reuse the same one. +#pragma warning disable RSEXPERIMENTAL001 // sym-shipped usage of experimental API + if (semanticModel.NullableAnalysisIsDisabled) + _activeDocumentNullableDisabledSemanticModel = semanticModel; + else + _activeDocumentSemanticModel = semanticModel; #pragma warning restore RSEXPERIMENTAL001 + } } } From 6f1271ae269bc8df587f7a3256461ebfd229a7c8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 16:37:29 -0700 Subject: [PATCH 065/292] Remove --- .../DocumentTracking/DefaultDocumentTrackingService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs index 3832ac9faed5b..596cf877c78ab 100644 --- a/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs +++ b/src/Workspaces/Core/Portable/Workspace/DocumentTracking/DefaultDocumentTrackingService.cs @@ -14,8 +14,6 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DefaultDocumentTrackingService() : IDocumentTrackingService { - public bool SupportsDocumentTracking => false; - public event EventHandler ActiveDocumentChanged { add { } remove { } } public ImmutableArray GetVisibleDocuments() From a8e7e877d50b7c63c82865b799d4691484bd62ef Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 17:03:00 -0700 Subject: [PATCH 066/292] Remove --- .../TestUtilities/NavigateTo/AbstractNavigateToTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs index e318768462e40..d6a0a99528364 100644 --- a/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs +++ b/src/EditorFeatures/TestUtilities/NavigateTo/AbstractNavigateToTests.cs @@ -238,10 +238,7 @@ private class FirstDocIsVisibleDocumentTrackingService : IDocumentTrackingServic private FirstDocIsVisibleDocumentTrackingService(Workspace workspace) => _workspace = workspace; - public bool SupportsDocumentTracking => true; - public event EventHandler ActiveDocumentChanged { add { } remove { } } - public event EventHandler NonRoslynBufferTextChanged { add { } remove { } } public DocumentId TryGetActiveDocument() => null; From 08a45dd579222659045e0fd8bf50defa043ded8c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 17:05:10 -0700 Subject: [PATCH 067/292] Remove --- .../VisualStudioActiveDocumentTracker.cs | 36 +--------------- ...ualStudioDocumentTrackingServiceFactory.cs | 41 ++++--------------- 2 files changed, 8 insertions(+), 69 deletions(-) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs index 83d642ae978f9..d71414b9d2b9a 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs @@ -7,20 +7,15 @@ using System.ComponentModel.Composition; using System.Linq; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; @@ -35,7 +30,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; internal sealed class VisualStudioActiveDocumentTracker : IVsSelectionEvents { private readonly IThreadingContext _threadingContext; - private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; /// /// The list of tracked frames. This can only be written by the UI thread, although can be read (with care) from any thread. @@ -51,11 +45,9 @@ internal sealed class VisualStudioActiveDocumentTracker : IVsSelectionEvents [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioActiveDocumentTracker( IThreadingContext threadingContext, - [Import(typeof(SVsServiceProvider))] IAsyncServiceProvider asyncServiceProvider, - IVsEditorAdaptersFactoryService editorAdaptersFactoryService) + [Import(typeof(SVsServiceProvider))] IAsyncServiceProvider asyncServiceProvider) { _threadingContext = threadingContext; - _editorAdaptersFactoryService = editorAdaptersFactoryService; _threadingContext.RunWithShutdownBlockAsync(async cancellationToken => { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); @@ -84,11 +76,6 @@ public VisualStudioActiveDocumentTracker( /// public event EventHandler? DocumentsChanged; - /// - /// Raised when a non-Roslyn text buffer is edited, which can be used to back off of expensive background processing. May be raised on any thread. - /// - public event EventHandler? NonRoslynBufferTextChanged; - /// /// Returns the of the active document in a given . /// @@ -237,9 +224,6 @@ public FrameListener(VisualStudioActiveDocumentTracker service, IVsWindowFrame f TryInitializeTextBuffer(); } - private void NonRoslynTextBuffer_Changed(object sender, TextContentChangedEventArgs e) - => _documentTracker.NonRoslynBufferTextChanged?.Invoke(_documentTracker, EventArgs.Empty); - /// /// Returns the current DocumentId for this window frame. Care must be made with this value, since "current" could change asynchronously as the document /// could be unregistered from a workspace. @@ -312,19 +296,6 @@ private void TryInitializeTextBuffer() } } - if (ErrorHandler.Succeeded(Frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out var docData))) - { - if (docData is IVsTextBuffer bufferAdapter) - { - TextBuffer = _documentTracker._editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter); - - if (TextBuffer != null && !TextBuffer.ContentType.IsOfType(ContentTypeNames.RoslynContentType)) - { - TextBuffer.Changed += NonRoslynTextBuffer_Changed; - } - } - } - return; } @@ -333,11 +304,6 @@ private int Disconnect() _documentTracker._threadingContext.ThrowIfNotOnUIThread(); _documentTracker.RemoveFrame(this); - if (TextBuffer != null) - { - TextBuffer.Changed -= NonRoslynTextBuffer_Changed; - } - if (_frameEventsCookie != VSConstants.VSCOOKIE_NIL) { return ((IVsWindowFrame2)Frame).Unadvise(_frameEventsCookie); diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs index d2ef8a9aca10b..2273efa36396d 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentTrackingServiceFactory.cs @@ -12,35 +12,21 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; [ExportWorkspaceServiceFactory(typeof(IDocumentTrackingService), ServiceLayer.Host), Shared] -internal sealed class VisualStudioDocumentTrackingServiceFactory : IWorkspaceServiceFactory +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VisualStudioDocumentTrackingServiceFactory(VisualStudioActiveDocumentTracker activeDocumentTracker) : IWorkspaceServiceFactory { - private readonly VisualStudioActiveDocumentTracker _activeDocumentTracker; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioDocumentTrackingServiceFactory(VisualStudioActiveDocumentTracker activeDocumentTracker) - => _activeDocumentTracker = activeDocumentTracker; - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new VisualStudioDocumentTrackingService(_activeDocumentTracker, workspaceServices.Workspace); + => new VisualStudioDocumentTrackingService(activeDocumentTracker, workspaceServices.Workspace); - private class VisualStudioDocumentTrackingService : IDocumentTrackingService + private class VisualStudioDocumentTrackingService(VisualStudioActiveDocumentTracker activeDocumentTracker, Workspace workspace) : IDocumentTrackingService { - private readonly VisualStudioActiveDocumentTracker _activeDocumentTracker; - private readonly Workspace _workspace; - - public VisualStudioDocumentTrackingService(VisualStudioActiveDocumentTracker activeDocumentTracker, Workspace workspace) - { - _activeDocumentTracker = activeDocumentTracker; - _workspace = workspace; - } - + private readonly VisualStudioActiveDocumentTracker _activeDocumentTracker = activeDocumentTracker; + private readonly Workspace _workspace = workspace; private readonly object _gate = new(); private int _subscriptions = 0; private EventHandler? _activeDocumentChangedEventHandler; - public bool SupportsDocumentTracking => true; - public event EventHandler ActiveDocumentChanged { add @@ -77,19 +63,6 @@ public event EventHandler ActiveDocumentChanged private void ActiveDocumentTracker_DocumentsChanged(object? sender, EventArgs e) => _activeDocumentChangedEventHandler?.Invoke(this, TryGetActiveDocument()); - public event EventHandler NonRoslynBufferTextChanged - { - add - { - _activeDocumentTracker.NonRoslynBufferTextChanged += value; - } - - remove - { - _activeDocumentTracker.NonRoslynBufferTextChanged -= value; - } - } - public DocumentId? TryGetActiveDocument() => _activeDocumentTracker.TryGetActiveDocument(_workspace); From 118a257185dccda12f0951f9c747a2fedd34e63c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 17:17:21 -0700 Subject: [PATCH 068/292] Simplify --- .../DocumentTracking/TestDocumentTrackingService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs index 44fe5a20d19c0..ec01c8c69affc 100644 --- a/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs +++ b/src/EditorFeatures/TestUtilities/DocumentTracking/TestDocumentTrackingService.cs @@ -28,5 +28,5 @@ public void SetActiveDocument(DocumentId? newActiveDocumentId) => _activeDocumentId; public ImmutableArray GetVisibleDocuments() - => _activeDocumentId != null ? ImmutableArray.Create(_activeDocumentId) : ImmutableArray.Empty; + => _activeDocumentId != null ? [_activeDocumentId] : []; } From b99374bf42345619c7e6d547d765acadf43a5cb4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 17:39:55 -0700 Subject: [PATCH 069/292] rename file --- ...ocumentTrackingService.cs => RemoteDocumentTrackingService.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Workspaces/Remote/ServiceHub/Services/{ServiceHubDocumentTrackingService.cs => RemoteDocumentTrackingService.cs} (100%) diff --git a/src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs b/src/Workspaces/Remote/ServiceHub/Services/RemoteDocumentTrackingService.cs similarity index 100% rename from src/Workspaces/Remote/ServiceHub/Services/ServiceHubDocumentTrackingService.cs rename to src/Workspaces/Remote/ServiceHub/Services/RemoteDocumentTrackingService.cs From 83bfccc4b1dd716a1af285121b83a8e3ffc7ada7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 17:41:25 -0700 Subject: [PATCH 070/292] simplify --- src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs index 12b1fafbd9d8f..805117ccedac8 100644 --- a/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs +++ b/src/EditorFeatures/Core/Remote/SolutionChecksumUpdater.cs @@ -82,7 +82,6 @@ public SolutionChecksumUpdater( // start listening workspace change event _workspace.WorkspaceChanged += OnWorkspaceChanged; - _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; if (_globalOperationService != null) @@ -101,7 +100,6 @@ public void Shutdown() PauseWork(); _documentTrackingService.ActiveDocumentChanged -= OnActiveDocumentChanged; - _workspace.WorkspaceChanged -= OnWorkspaceChanged; if (_globalOperationService != null) From 06cc53f711f9b8b231efae45a83c50030b55ff8d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:04:32 -0700 Subject: [PATCH 071/292] Use 'nullable disabled' semantic model when computing FAR results --- .../FindReferences/FindReferenceCache.cs | 23 +++++++++++-------- .../FindReferencesDocumentState.cs | 6 ++--- .../FindReferencesSearchEngine.cs | 5 ++-- ...sSearchEngine_FindReferencesInDocuments.cs | 5 ++-- .../Core/Extensions/DocumentExtensions.cs | 15 ++++++++++++ 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index 05efca6bffc64..e00a5fca66382 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -21,10 +21,15 @@ internal sealed class FindReferenceCache { private static readonly ConditionalWeakTable s_cache = new(); - public static FindReferenceCache GetCache(SemanticModel model) - => s_cache.GetValue(model, static model => new(model)); + public static async ValueTask GetCacheAsync(Document document, CancellationToken cancellationToken) + { + // Find-Refs is not impacted by nullable types at all. So get a nullable-disabled semantic model to avoid + // unnecessary costs while binding. + var model = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); + return s_cache.GetValue(model, static model => new(model)); + } - private readonly SemanticModel _semanticModel; + public readonly SemanticModel SemanticModel; private readonly ConcurrentDictionary _symbolInfoCache = []; private readonly ConcurrentDictionary> _identifierCache; @@ -34,7 +39,7 @@ public static FindReferenceCache GetCache(SemanticModel model) private FindReferenceCache(SemanticModel semanticModel) { - _semanticModel = semanticModel; + SemanticModel = semanticModel; _identifierCache = new(comparer: semanticModel.Language switch { LanguageNames.VisualBasic => StringComparer.OrdinalIgnoreCase, @@ -44,21 +49,19 @@ private FindReferenceCache(SemanticModel semanticModel) } public SymbolInfo GetSymbolInfo(SyntaxNode node, CancellationToken cancellationToken) - { - return _symbolInfoCache.GetOrAdd(node, static (n, arg) => arg._semanticModel.GetSymbolInfo(n, arg.cancellationToken), (_semanticModel, cancellationToken)); - } + => _symbolInfoCache.GetOrAdd(node, static (n, arg) => arg.SemanticModel.GetSymbolInfo(n, arg.cancellationToken), (SemanticModel, cancellationToken)); public IAliasSymbol? GetAliasInfo( ISemanticFactsService semanticFacts, SyntaxToken token, CancellationToken cancellationToken) { if (_aliasNameSet == null) { - var set = semanticFacts.GetAliasNameSet(_semanticModel, cancellationToken); + var set = semanticFacts.GetAliasNameSet(SemanticModel, cancellationToken); Interlocked.CompareExchange(ref _aliasNameSet, set, null); } if (_aliasNameSet.Contains(token.ValueText)) - return _semanticModel.GetAliasInfo(token.GetRequiredParent(), cancellationToken); + return SemanticModel.GetAliasInfo(token.GetRequiredParent(), cancellationToken); return null; } @@ -95,7 +98,7 @@ static async ValueTask> ComputeAndCacheTokensAsync( FindReferenceCache cache, Document document, string identifier, SyntaxTreeIndex info, CancellationToken cancellationToken) { var syntaxFacts = document.GetRequiredLanguageService(); - var root = await cache._semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + var root = await cache.SemanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); // If the identifier was escaped in the file then we'll have to do a more involved search that actually // walks the root and checks all identifier tokens. diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs index 59d513ef3bc73..bd5bf585dbfde 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs @@ -10,7 +10,6 @@ namespace Microsoft.CodeAnalysis.FindSymbols; internal class FindReferencesDocumentState( Document document, - SemanticModel semanticModel, SyntaxNode root, FindReferenceCache cache, HashSet? globalAliases) @@ -18,13 +17,14 @@ internal class FindReferencesDocumentState( private static readonly HashSet s_empty = []; public readonly Document Document = document; - public readonly SemanticModel SemanticModel = semanticModel; public readonly SyntaxNode Root = root; public readonly FindReferenceCache Cache = cache; public readonly HashSet GlobalAliases = globalAliases ?? s_empty; public readonly Solution Solution = document.Project.Solution; - public readonly SyntaxTree SyntaxTree = semanticModel.SyntaxTree; public readonly ISyntaxFactsService SyntaxFacts = document.GetRequiredLanguageService(); public readonly ISemanticFactsService SemanticFacts = document.GetRequiredLanguageService(); + + public SemanticModel SemanticModel => Cache.SemanticModel; + public SyntaxTree SyntaxTree => SemanticModel.SyntaxTree; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index b29348e91c29c..274bd87b4b2c6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -262,15 +262,14 @@ private async Task ProcessDocumentAsync( // the symbol being searched for). As such, we're almost certainly going to have to do semantic checks // to now see if the candidate actually matches the symbol. This will require syntax and semantics. So // just grab those once here and hold onto them for the lifetime of this call. - var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var cache = FindReferenceCache.GetCache(model); + var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); foreach (var symbol in symbols) { var globalAliases = TryGet(symbolToGlobalAliases, symbol); var state = new FindReferencesDocumentState( - document, model, root, cache, globalAliases); + document, root, cache, globalAliases); await ProcessDocumentAsync(symbol, state).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 6c5dc334823e7..89cebc68d69a5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -87,14 +87,13 @@ async ValueTask PerformSearchInDocumentAsync( // appropriate finders checking this document for hits. We're likely going to need to perform syntax // and semantics checks in this file. So just grab those once here and hold onto them for the lifetime // of this call. - var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var cache = FindReferenceCache.GetCache(model); + var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); foreach (var symbol in symbols) { var globalAliases = GetGlobalAliasesSet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(document, model, root, cache, globalAliases); + var state = new FindReferencesDocumentState(document, root, cache, globalAliases); await PerformSearchInDocumentWorkerAsync(symbol, document, state).ConfigureAwait(false); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs index 2d2c83b236f21..d7d5fa8da2911 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs @@ -41,6 +41,21 @@ public static async ValueTask GetRequiredSemanticModelAsync(this return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } +#if !CODE_STYLE + + public static async ValueTask GetRequiredNullableDisabledSemanticModelAsync(this Document document, CancellationToken cancellationToken) + { + if (document.TryGetNullableDisabledSemanticModel(out var semanticModel)) + return semanticModel; + +#pragma warning disable RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + semanticModel = await document.GetSemanticModelAsync(SemanticModelOptions.DisableNullableAnalysis, cancellationToken).ConfigureAwait(false); +#pragma warning restore RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); + } + +#endif + public static async ValueTask GetRequiredSyntaxTreeAsync(this Document document, CancellationToken cancellationToken) { if (document.TryGetSyntaxTree(out var syntaxTree)) From b9119f811b4387e339b6ea4bc491555c490af273 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:16:12 -0700 Subject: [PATCH 072/292] simplify --- .../FindReferences/FindReferenceCache.cs | 27 ++++++++++++++----- .../FindReferencesDocumentState.cs | 21 ++++++++------- ...sSearchEngine_FindReferencesInDocuments.cs | 7 +++-- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index e00a5fca66382..aaad258c3e4e3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -17,19 +17,32 @@ namespace Microsoft.CodeAnalysis.FindSymbols; +/// +/// Caches information find-references needs associated with each document. Computed and cached so that multiple calls +/// to find-references in a row can share the same data. +/// internal sealed class FindReferenceCache { - private static readonly ConditionalWeakTable s_cache = new(); + private static readonly ConditionalWeakTable> s_cache = new(); public static async ValueTask GetCacheAsync(Document document, CancellationToken cancellationToken) { - // Find-Refs is not impacted by nullable types at all. So get a nullable-disabled semantic model to avoid - // unnecessary costs while binding. - var model = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); - return s_cache.GetValue(model, static model => new(model)); + var lazy = s_cache.GetValue(document, static document => AsyncLazy.Create(ComputeCacheAsync, document)); + return await lazy.GetValueAsync(cancellationToken).ConfigureAwait(false); + + static async Task ComputeCacheAsync(Document document, CancellationToken cancellationToken) + { + // Find-Refs is not impacted by nullable types at all. So get a nullable-disabled semantic model to avoid + // unnecessary costs while binding. + var model = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return new(document, model, root); + } } + public readonly Document Document; public readonly SemanticModel SemanticModel; + public readonly SyntaxNode Root; private readonly ConcurrentDictionary _symbolInfoCache = []; private readonly ConcurrentDictionary> _identifierCache; @@ -37,9 +50,11 @@ public static async ValueTask GetCacheAsync(Document documen private ImmutableHashSet? _aliasNameSet; private ImmutableArray _constructorInitializerCache; - private FindReferenceCache(SemanticModel semanticModel) + private FindReferenceCache(Document document, SemanticModel semanticModel, SyntaxNode root) { + Document = document; SemanticModel = semanticModel; + Root = root; _identifierCache = new(comparer: semanticModel.Language switch { LanguageNames.VisualBasic => StringComparer.OrdinalIgnoreCase, diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs index bd5bf585dbfde..c24f5caae2c8b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs @@ -8,23 +8,24 @@ namespace Microsoft.CodeAnalysis.FindSymbols; -internal class FindReferencesDocumentState( - Document document, - SyntaxNode root, +/// +/// Ephemeral information that find-references needs for a particular document when searching for a specific +/// symbol. Importantly, it contains the global aliases to that symbol within the current project. +internal sealed class FindReferencesDocumentState( FindReferenceCache cache, HashSet? globalAliases) { private static readonly HashSet s_empty = []; - public readonly Document Document = document; - public readonly SyntaxNode Root = root; public readonly FindReferenceCache Cache = cache; public readonly HashSet GlobalAliases = globalAliases ?? s_empty; - public readonly Solution Solution = document.Project.Solution; - public readonly ISyntaxFactsService SyntaxFacts = document.GetRequiredLanguageService(); - public readonly ISemanticFactsService SemanticFacts = document.GetRequiredLanguageService(); + public Document Document => this.Cache.Document; + public SyntaxNode Root => this.Cache.Root; + public SemanticModel SemanticModel => this.Cache.SemanticModel; + public SyntaxTree SyntaxTree => this.SemanticModel.SyntaxTree; - public SemanticModel SemanticModel => Cache.SemanticModel; - public SyntaxTree SyntaxTree => SemanticModel.SyntaxTree; + public Solution Solution => this.Document.Project.Solution; + public ISyntaxFactsService SyntaxFacts => this.Document.GetRequiredLanguageService(); + public ISemanticFactsService SemanticFacts => this.Document.GetRequiredLanguageService(); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 89cebc68d69a5..62263a56ebb80 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -87,20 +87,19 @@ async ValueTask PerformSearchInDocumentAsync( // appropriate finders checking this document for hits. We're likely going to need to perform syntax // and semantics checks in this file. So just grab those once here and hold onto them for the lifetime // of this call. - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); foreach (var symbol in symbols) { var globalAliases = GetGlobalAliasesSet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState(document, root, cache, globalAliases); + var state = new FindReferencesDocumentState(cache, globalAliases); - await PerformSearchInDocumentWorkerAsync(symbol, document, state).ConfigureAwait(false); + await PerformSearchInDocumentWorkerAsync(symbol, state).ConfigureAwait(false); } } async ValueTask PerformSearchInDocumentWorkerAsync( - ISymbol symbol, Document document, FindReferencesDocumentState state) + ISymbol symbol, FindReferencesDocumentState state) { // Always perform a normal search, looking for direct references to exactly that symbol. foreach (var finder in _finders) From c0317c33a958e434d7dc8e0e76bab402ccc1fa00 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:22:24 -0700 Subject: [PATCH 073/292] Simplify --- .../FindReferences/FindReferencesSearchEngine.cs | 7 +++---- ...FindReferencesSearchEngine_FindReferencesInDocuments.cs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 274bd87b4b2c6..871ac5bc3bbf0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -268,8 +268,8 @@ private async Task ProcessDocumentAsync( foreach (var symbol in symbols) { var globalAliases = TryGet(symbolToGlobalAliases, symbol); - var state = new FindReferencesDocumentState( - document, root, cache, globalAliases); + var state = new FindReferencesDocumentState(cache, globalAliases); + await ProcessDocumentAsync(symbol, state).ConfigureAwait(false); } } @@ -279,8 +279,7 @@ private async Task ProcessDocumentAsync( } async Task ProcessDocumentAsync( - ISymbol symbol, - FindReferencesDocumentState state) + ISymbol symbol, FindReferencesDocumentState state) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 62263a56ebb80..0e982257b278e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -91,7 +91,7 @@ async ValueTask PerformSearchInDocumentAsync( foreach (var symbol in symbols) { - var globalAliases = GetGlobalAliasesSet(symbolToGlobalAliases, symbol); + var globalAliases = TryGet(symbolToGlobalAliases, symbol); var state = new FindReferencesDocumentState(cache, globalAliases); await PerformSearchInDocumentWorkerAsync(symbol, state).ConfigureAwait(false); From 53046e24aeca419a77067283d8848818da5b5564 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:42:43 -0700 Subject: [PATCH 074/292] Use 'nullable disabled' semantic model when computing doc highlights results --- .../AbstractDocumentHighlightsService.cs | 4 +++- .../Core/Extensions/DocumentExtensions.cs | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs index 1671cca3ac7fd..f4075ce374447 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs @@ -63,7 +63,9 @@ public async Task> GetDocumentHighlightsAsync private async Task> GetDocumentHighlightsInCurrentProcessAsync( Document document, int position, IImmutableSet documentsToSearch, HighlightingOptions options, CancellationToken cancellationToken) { - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + // Document highlights are not impacted by nullable analysis. Get a semantic model with nullability disabled to + // lower the amount of work we need to do here. + var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); var result = TryGetEmbeddedLanguageHighlights(document, semanticModel, position, options, cancellationToken); if (!result.IsDefaultOrEmpty) return result; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs index 2d2c83b236f21..d7d5fa8da2911 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs @@ -41,6 +41,21 @@ public static async ValueTask GetRequiredSemanticModelAsync(this return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } +#if !CODE_STYLE + + public static async ValueTask GetRequiredNullableDisabledSemanticModelAsync(this Document document, CancellationToken cancellationToken) + { + if (document.TryGetNullableDisabledSemanticModel(out var semanticModel)) + return semanticModel; + +#pragma warning disable RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + semanticModel = await document.GetSemanticModelAsync(SemanticModelOptions.DisableNullableAnalysis, cancellationToken).ConfigureAwait(false); +#pragma warning restore RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + return semanticModel ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); + } + +#endif + public static async ValueTask GetRequiredSyntaxTreeAsync(this Document document, CancellationToken cancellationToken) { if (document.TryGetSyntaxTree(out var syntaxTree)) From 411356a688896e3779da325de226295f24d1d94d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:45:02 -0700 Subject: [PATCH 075/292] Use 'nullable disabled' semantic model when computing doc highlights results --- .../DocumentHighlighting/CSharpDocumentHighlightsService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs index c8a48212c6e61..e872267221e35 100644 --- a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs +++ b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs @@ -56,7 +56,9 @@ protected override async Task> GetAdditionalReferencesA if (type.IsVar) { - semanticModel ??= await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + // Document highlights are not impacted by nullable analysis. Get a semantic model with nullability + // disabled to lower the amount of work we need to do here. + semanticModel ??= await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); var boundSymbol = semanticModel.GetSymbolInfo(type, cancellationToken).Symbol; boundSymbol = boundSymbol?.OriginalDefinition; From 92cf500315e08c9a477083ee868498cba22a05e6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:45:35 -0700 Subject: [PATCH 076/292] Wrap --- .../CSharpDocumentHighlightsService.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs index e872267221e35..e472dfb91502e 100644 --- a/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs +++ b/src/Features/CSharp/Portable/DocumentHighlighting/CSharpDocumentHighlightsService.cs @@ -24,10 +24,12 @@ namespace Microsoft.CodeAnalysis.CSharp.DocumentHighlighting; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class CSharpDocumentHighlightsService( - [ImportMany] IEnumerable> services) : AbstractDocumentHighlightsService(LanguageNames.CSharp, - CSharpEmbeddedLanguagesProvider.Info, - CSharpSyntaxKinds.Instance, - services) + [ImportMany] IEnumerable> services) + : AbstractDocumentHighlightsService( + LanguageNames.CSharp, + CSharpEmbeddedLanguagesProvider.Info, + CSharpSyntaxKinds.Instance, + services) { protected override async Task> GetAdditionalReferencesAsync( Document document, ISymbol symbol, CancellationToken cancellationToken) From ba84fd8730ff9ffe5af1fb36cd541cdc12ee1171 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 19:51:00 -0700 Subject: [PATCH 077/292] fix --- .../FindSymbols/FindReferences/FindReferencesDocumentState.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs index c24f5caae2c8b..7a2fb1f8c7d6a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesDocumentState.cs @@ -11,6 +11,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols; /// /// Ephemeral information that find-references needs for a particular document when searching for a specific /// symbol. Importantly, it contains the global aliases to that symbol within the current project. +/// internal sealed class FindReferencesDocumentState( FindReferenceCache cache, HashSet? globalAliases) From 330c12eb3accf00c015eb3db499588f77038ae0e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 22:10:43 -0700 Subject: [PATCH 078/292] remove' git push --- .../FindSymbols/FindReferences/FindReferencesSearchEngine.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 871ac5bc3bbf0..8d788e83130bb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -262,7 +262,6 @@ private async Task ProcessDocumentAsync( // the symbol being searched for). As such, we're almost certainly going to have to do semantic checks // to now see if the candidate actually matches the symbol. This will require syntax and semantics. So // just grab those once here and hold onto them for the lifetime of this call. - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); foreach (var symbol in symbols) From 635483895c5cd86d33be782aa05c2c89cd1061ab Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 23:02:29 -0700 Subject: [PATCH 079/292] Add test --- .../Syntax/Syntax/SyntaxEquivalenceTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs index 00f2a1223c791..73ee8135e6e75 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs @@ -5,6 +5,7 @@ #nullable disable using System; +using System.Linq; using Roslyn.Test.Utilities; using Xunit; @@ -1284,5 +1285,33 @@ void M() VerifyNotEquivalent(tree1, tree2, topLevel: false); VerifyEquivalent(tree1, tree2, topLevel: true); } + + [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2018744")] + public void TestDeeplyNested() + { + var expr = string.Join(" + ", Enumerable.Range(0, 10000).Select(_ => "a")); + + var tree1 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a) + { + var v = {{expr}}; + } + } + """"); + + var tree2 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a) + { + var v = {{expr}}; + } + } + """"); + + VerifyEquivalent(tree1, tree2, topLevel: false); + } } } From ce56b1ced2d3b7219ffe3e0d44052a5ac8ee1ae5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 23:04:37 -0700 Subject: [PATCH 080/292] add test --- .../Syntax/Syntax/SyntaxEquivalenceTests.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs index 73ee8135e6e75..4094cc28447d9 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxEquivalenceTests.cs @@ -1287,7 +1287,35 @@ void M() } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2018744")] - public void TestDeeplyNested() + public void TestDeeplyNested1() + { + var expr = string.Join(" + ", Enumerable.Range(0, 10000).Select(_ => "a")); + + var tree1 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a, int b, int c) + { + var v = {{expr}} + b; + } + } + """"); + + var tree2 = SyntaxFactory.ParseSyntaxTree($$"""" + class C + { + void M(int a, int b, int c) + { + var v = {{expr}} + c; + } + } + """"); + + VerifyNotEquivalent(tree1, tree2, topLevel: false); + } + + [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2018744")] + public void TestDeeplyNested2() { var expr = string.Join(" + ", Enumerable.Range(0, 10000).Select(_ => "a")); From 1037a132eab9b37bfb3fa1bee1399870a08be0ae Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 17 Apr 2024 23:52:05 -0700 Subject: [PATCH 081/292] fix --- .../Def/Workspace/VisualStudioActiveDocumentTracker.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs index 0dbf4b1f2e57f..73ead9d0ebedc 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioActiveDocumentTracker.cs @@ -13,9 +13,11 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; @@ -30,6 +32,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation; internal sealed class VisualStudioActiveDocumentTracker : IVsSelectionEvents { private readonly IThreadingContext _threadingContext; + private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; /// /// The list of tracked frames. This can only be written by the UI thread, although can be read (with care) from any thread. @@ -45,9 +48,11 @@ internal sealed class VisualStudioActiveDocumentTracker : IVsSelectionEvents [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public VisualStudioActiveDocumentTracker( IThreadingContext threadingContext, - [Import(typeof(SVsServiceProvider))] IAsyncServiceProvider asyncServiceProvider) + [Import(typeof(SVsServiceProvider))] IAsyncServiceProvider asyncServiceProvider, + IVsEditorAdaptersFactoryService editorAdaptersFactoryService) { _threadingContext = threadingContext; + _editorAdaptersFactoryService = editorAdaptersFactoryService; _threadingContext.RunWithShutdownBlockAsync(async cancellationToken => { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); From 1bf23610b41c148e1cf2d4144d0dbd6714b1f145 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Thu, 18 Apr 2024 11:36:24 -0700 Subject: [PATCH 082/292] Fix issue in editor config filepath comparison (#73033) This code previously just did a simple string.StartsWith to check if a file was in a folder. However, this would incorrectly return true for a case such as testing if c:\foo\test.txt was in folder c:\foo\test. --- .../Analyzers/AnalyzerConfigTests.cs | 22 +++++++++++++++++++ .../FileSystem/PathUtilitiesTests.cs | 21 ++++++++++++++++++ .../Portable/CommandLine/AnalyzerConfigSet.cs | 2 +- .../Core/Portable/FileSystem/PathUtilities.cs | 4 ++-- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs index d309e6fad88f4..0fb4efa394de5 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs @@ -1083,6 +1083,28 @@ public void MultipleEditorConfigs() }, options.Select(o => o.TreeOptions).ToArray()); } + [Fact] + public void FolderNamePrefixOfFileName() + { + var configs = ArrayBuilder.GetInstance(); + configs.Add(Parse(@" +[*.cs] +dotnet_diagnostic.cs000.severity = suggestion", "/root/.editorconfig")); + configs.Add(Parse(@" +root=true", "/root/test/.editorconfig")); + + var options = GetAnalyzerConfigOptions( + new[] { "/root/testing.cs" }, + configs); + configs.Free(); + + Assert.Equal(new[] + { + CreateImmutableDictionary( + ("cs000", ReportDiagnostic.Info)), + }, options.Select(o => o.TreeOptions).ToArray()); + } + [Fact] public void InheritOuterConfig() { diff --git a/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs b/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs index c660ffa62b568..123f0ba58d5d1 100644 --- a/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/FileSystem/PathUtilitiesTests.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.IO; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -291,6 +292,26 @@ public void IsSameDirectoryOrChildOfNegativeTests() Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\A\B\C", @"C:\A\B\C\D")); } + [ConditionalFact(typeof(WindowsOnly))] + public void IsSameDirectoryOrChildOfSpecifyingCaseSensitivity_Windows() + { + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\a\B\C", @"C:\A\B", StringComparison.OrdinalIgnoreCase)); + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"C:\A\b\C", @"C:\A\B", StringComparison.OrdinalIgnoreCase)); + + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\a\B\C", @"C:\A\B", StringComparison.Ordinal)); + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"C:\A\b\C", @"C:\A\B", StringComparison.Ordinal)); + } + + [ConditionalFact(typeof(UnixLikeOnly))] + public void IsSameDirectoryOrChildOfSpecifyingCaseSensitivity_Unix() + { + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"/a/B/C", @"/A/B", StringComparison.OrdinalIgnoreCase)); + Assert.True(PathUtilities.IsSameDirectoryOrChildOf(@"/A/b/C", @"/A/B", StringComparison.OrdinalIgnoreCase)); + + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"/a/B/C", @"/A/B", StringComparison.Ordinal)); + Assert.False(PathUtilities.IsSameDirectoryOrChildOf(@"/A/b/C", @"/A/B", StringComparison.Ordinal)); + } + [Fact] public void IsValidFilePath() { diff --git a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs index eaa8de216fe95..2296b51ec33ce 100644 --- a/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs +++ b/src/Compilers/Core/Portable/CommandLine/AnalyzerConfigSet.cs @@ -204,7 +204,7 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath) { var config = _analyzerConfigs[analyzerConfigIndex]; - if (normalizedPath.StartsWith(config.NormalizedDirectory, StringComparison.Ordinal)) + if (PathUtilities.IsSameDirectoryOrChildOf(normalizedPath, config.NormalizedDirectory, StringComparison.Ordinal)) { // If this config is a root config, then clear earlier options since they don't apply // to this source file. diff --git a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs index 0617a16e08dfd..570121ad3d3bb 100644 --- a/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs +++ b/src/Compilers/Core/Portable/FileSystem/PathUtilities.cs @@ -159,7 +159,7 @@ public static string RemoveExtension(string path) return null; } - internal static bool IsSameDirectoryOrChildOf(string child, string parent) + internal static bool IsSameDirectoryOrChildOf(string child, string parent, StringComparison comparison = StringComparison.OrdinalIgnoreCase) { parent = RemoveTrailingDirectorySeparator(parent); string? currentChild = child; @@ -167,7 +167,7 @@ internal static bool IsSameDirectoryOrChildOf(string child, string parent) { currentChild = RemoveTrailingDirectorySeparator(currentChild); - if (currentChild.Equals(parent, StringComparison.OrdinalIgnoreCase)) + if (currentChild.Equals(parent, comparison)) { return true; } From 3aaabf34a3cf0ce18135e3433c525a6a08d86ada Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 11:45:28 -0700 Subject: [PATCH 083/292] Change how we yield --- .../Core.Wpf/Suggestions/SuggestedActionsSource.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index be74c5242f47f..af3f792840d2a 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -21,6 +21,7 @@ using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; using IUIThreadOperationContext = Microsoft.VisualStudio.Utilities.IUIThreadOperationContext; @@ -191,7 +192,7 @@ private void OnTextViewClosed(object sender, EventArgs e) await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); var selection = TryGetCodeRefactoringSelection(state, range); - await Task.Yield().ConfigureAwait(false); + await TaskScheduler.Default; // If we have a selection, kick off the work to get refactorings concurrently with the above work to get errors. var refactoringTask = selection != null @@ -210,7 +211,7 @@ private void OnTextViewClosed(object sender, EventArgs e) async Task GetFixLevelAsync() { // Ensure we yield the thread that called into us, allowing it to continue onwards. - await Task.Yield().ConfigureAwait(false); + await TaskScheduler.Default; var lowPriorityAnalyzers = new ConcurrentSet(); foreach (var order in Orderings) @@ -257,7 +258,7 @@ private void OnTextViewClosed(object sender, EventArgs e) async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) { // Ensure we yield the thread that called into us, allowing it to continue onwards. - await Task.Yield().ConfigureAwait(false); + await TaskScheduler.Default; if (!selection.HasValue) { From 6b9ccdb03fe4b126a754c33e47547d5ef596a27a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 12:30:22 -0700 Subject: [PATCH 084/292] Move to loading text lazily --- .../Serialization/SerializableSourceText.cs | 33 +++++++++++++++++-- .../Remote/Core/AbstractAssetProvider.cs | 3 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 00c816d4c0e31..bf0beda4eaf3d 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -5,9 +5,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; -using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; @@ -190,4 +188,35 @@ public static SerializableSourceText Deserialize( contentHash); } } + + public TextLoader ToTextLoader(string? filePath) + => new SerializableSourceTextLoader(this, filePath); + + private sealed class SerializableSourceTextLoader : TextLoader + { + private readonly AsyncLazy _lazyTextAndVersion; + + public SerializableSourceTextLoader( + SerializableSourceText text, + string? filePath) + { + var version = VersionStamp.Create(); + + this.FilePath = filePath; + _lazyTextAndVersion = AsyncLazy.Create( + async static (tuple, cancellationToken) => + TextAndVersion.Create(await tuple.text.GetTextAsync(cancellationToken).ConfigureAwait(false), tuple.version, tuple.filePath), + static (tuple, cancellationToken) => + TextAndVersion.Create(tuple.text.GetText(cancellationToken), tuple.version, tuple.filePath), + (text, version, filePath)); + } + + internal override string? FilePath { get; } + + public override Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) + => _lazyTextAndVersion.GetValueAsync(cancellationToken); + + internal override TextAndVersion LoadTextAndVersionSynchronously(LoadTextOptions options, CancellationToken cancellationToken) + => _lazyTextAndVersion.GetValue(cancellationToken); + } } diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 6a3c7c067f3ad..2cc7bf9e2192a 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -142,8 +142,7 @@ public async Task CreateDocumentInfoAsync( var attributes = await GetAssetAsync(new(AssetPathKind.DocumentAttributes, documentId), attributeChecksum, cancellationToken).ConfigureAwait(false); var serializableSourceText = await GetAssetAsync(new(AssetPathKind.DocumentText, documentId), textChecksum, cancellationToken).ConfigureAwait(false); - var text = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); - var textLoader = TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create(), attributes.FilePath)); + var textLoader = serializableSourceText.ToTextLoader(attributes.FilePath); // TODO: do we need version? return new DocumentInfo(attributes, textLoader, documentServiceProvider: null); From 5dd739df0f0299ef362fdfa33ff738b7769d544b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 12:33:20 -0700 Subject: [PATCH 085/292] Flesh out more --- .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 84b0ea0dd5915..5a44050959030 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -617,13 +617,14 @@ private async Task UpdateDocumentAsync( { var serializableSourceText = await _assetProvider.GetAssetAsync( assetPath: document.Id, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); - var sourceText = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); + var loader = serializableSourceText.ToTextLoader(document.FilePath); + var mode = PreservationMode.PreserveValue; document = document.Kind switch { - TextDocumentKind.Document => document.Project.Solution.WithDocumentText(document.Id, sourceText).GetDocument(document.Id)!, - TextDocumentKind.AnalyzerConfigDocument => document.Project.Solution.WithAnalyzerConfigDocumentText(document.Id, sourceText).GetAnalyzerConfigDocument(document.Id)!, - TextDocumentKind.AdditionalDocument => document.Project.Solution.WithAdditionalDocumentText(document.Id, sourceText).GetAdditionalDocument(document.Id)!, + TextDocumentKind.Document => document.Project.Solution.WithDocumentTextLoader(document.Id, loader, mode).GetRequiredDocument(document.Id), + TextDocumentKind.AnalyzerConfigDocument => document.Project.Solution.WithAnalyzerConfigDocumentTextLoader(document.Id, loader, mode).GetRequiredAnalyzerConfigDocument(document.Id), + TextDocumentKind.AdditionalDocument => document.Project.Solution.WithAdditionalDocumentTextLoader(document.Id, loader, mode).GetRequiredAdditionalDocument(document.Id), _ => throw ExceptionUtilities.UnexpectedValue(document.Kind), }; } From c2d66e812a97cc678ecf16579e027aa4ddc4ba7f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 13:21:44 -0700 Subject: [PATCH 086/292] Docs --- .../Portable/Serialization/SerializableSourceText.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index bf0beda4eaf3d..ea5752f33ecfc 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -192,6 +192,15 @@ public static SerializableSourceText Deserialize( public TextLoader ToTextLoader(string? filePath) => new SerializableSourceTextLoader(this, filePath); + /// + /// A that wraps a and provides access to the text in + /// a deferred fashion. In practice, during a host and OOP sync, while all the documents will be 'serialized' over + /// to OOP, the actual contents of the documents will only need to be loaded depending on which files are open, and + /// thus what compilations and trees are needed. As such, we want to be able to lazily defer actually getting the + /// contents of the text until it's actually needed. This loader allows us to do that, allowing the OOP side to + /// simply point to the segments in the memory-mapped-file the host has dumped its text into, and only actually + /// realizing the real text values when they're needed. + /// private sealed class SerializableSourceTextLoader : TextLoader { private readonly AsyncLazy _lazyTextAndVersion; From ed66d54b21ea6959a63c1eb178c8fb52814752c7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 13:41:20 -0700 Subject: [PATCH 087/292] docs --- .../Serialization/SerializableSourceText.cs | 16 ++++++++++++++-- .../Solution/ConstantTextAndVersionSource.cs | 6 ++++++ .../Solution/DocumentState_TreeTextSource.cs | 6 ++++++ .../Workspace/Solution/ITextAndVersionSource.cs | 6 ++++++ .../Solution/LoadableTextAndVersionSource.cs | 8 ++++---- .../Solution/RecoverableTextAndVersion.cs | 6 ++++++ .../Solution/TextDocumentState_Checksum.cs | 4 +--- 7 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index ea5752f33ecfc..3ef976b2aec21 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -112,12 +112,22 @@ public SourceText GetText(CancellationToken cancellationToken) public static ValueTask FromTextDocumentStateAsync( TextDocumentState state, CancellationToken cancellationToken) { - if (state.Storage is TemporaryTextStorage storage) + if (state.TextAndVersionSource.TextLoader is SerializableSourceTextLoader serializableLoader) { - return new ValueTask(new SerializableSourceText(storage, storage.ContentHash)); + // If we're already pointing at a serializable loader, we can just use that directly. + return new(serializableLoader.Text); + } + else if (state.Storage is TemporaryTextStorage storage) + { + // Otherwise, if we're pointing at a memory mapped storage location, we can create the source text that directly wraps that. + return new(new SerializableSourceText(storage, storage.ContentHash)); } else { + // Otherwise, the state object has reified the text into some other form, and dumped any original + // information on how it got it. In that case, we create a new text instance to represent the serializable + // source text out of. + return SpecializedTasks.TransformWithoutIntermediateCancellationExceptionAsync( static (state, cancellationToken) => state.GetTextAsync(cancellationToken), static (text, _) => new SerializableSourceText(text, text.GetContentHash()), @@ -203,12 +213,14 @@ public TextLoader ToTextLoader(string? filePath) /// private sealed class SerializableSourceTextLoader : TextLoader { + public readonly SerializableSourceText Text; private readonly AsyncLazy _lazyTextAndVersion; public SerializableSourceTextLoader( SerializableSourceText text, string? filePath) { + Text = text; var version = VersionStamp.Create(); this.FilePath = filePath; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs index 32714a34a1c44..3313ec6f1c845 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ConstantTextAndVersionSource.cs @@ -19,6 +19,12 @@ internal sealed class ConstantTextAndVersionSource(TextAndVersion value) : IText public bool CanReloadText => false; + /// + /// Not built from a text loader. + /// + public TextLoader? TextLoader + => null; + public TextAndVersion GetValue(LoadTextOptions options, CancellationToken cancellationToken) => _value; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs index 30eaca390f9cb..42553e056311c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_TreeTextSource.cs @@ -22,6 +22,12 @@ private sealed class TreeTextSource(AsyncLazy textSource, VersionSta public bool CanReloadText => false; + /// + /// Not created from a text loader. + /// + public TextLoader? TextLoader + => null; + public async Task GetValueAsync(LoadTextOptions options, CancellationToken cancellationToken) { var text = await textSource.GetValueAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs index ab0bc21554dde..ac7f86ac6a84e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs @@ -16,6 +16,12 @@ internal interface ITextAndVersionSource /// bool CanReloadText { get; } + /// + /// Retrieves the underlying if that's what this was + /// created from and still has access to. + /// + TextLoader? TextLoader { get; } + bool TryGetValue(LoadTextOptions options, [MaybeNullWhen(false)] out TextAndVersion value); TextAndVersion GetValue(LoadTextOptions options, CancellationToken cancellationToken); Task GetValueAsync(LoadTextOptions options, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs index 70417d9b9dbad..28a1f3f6fe04f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs @@ -35,10 +35,10 @@ private sealed class LazyValueWithOptions(LoadableTextAndVersionSource source, L private WeakReference? _weakInstance; private Task LoadAsync(CancellationToken cancellationToken) - => Source.Loader.LoadTextAsync(Options, cancellationToken); + => Source.TextLoader.LoadTextAsync(Options, cancellationToken); private TextAndVersion LoadSynchronously(CancellationToken cancellationToken) - => Source.Loader.LoadTextSynchronously(Options, cancellationToken); + => Source.TextLoader.LoadTextSynchronously(Options, cancellationToken); public bool TryGetValue([MaybeNullWhen(false)] out TextAndVersion value) { @@ -102,13 +102,13 @@ private void UpdateWeakAndStrongReferences_NoLock(TextAndVersion textAndVersion) } } - public readonly TextLoader Loader = loader; + public TextLoader TextLoader { get; } = loader; public readonly bool CacheResult = cacheResult; private LazyValueWithOptions? _lazyValue; public bool CanReloadText - => Loader.CanReloadText; + => TextLoader.CanReloadText; private LazyValueWithOptions GetLazyValue(LoadTextOptions options) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs index bcc498768b875..b4b327a618e44 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs @@ -42,6 +42,12 @@ private bool TryGetInitialSourceOrRecoverableText([NotNullWhen(true)] out ITextA return false; } + /// + /// Attempt to return the original loader if we still have it. + /// + public TextLoader? TextLoader + => (_initialSourceOrRecoverableText as ITextAndVersionSource)?.TextLoader; + public ITemporaryTextStorageInternal? Storage => (_initialSourceOrRecoverableText as RecoverableText)?.Storage; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs index 9c1513be2405e..d886e011e9112 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs @@ -36,11 +36,9 @@ private async Task ComputeChecksumsAsync(CancellationTok { using (Logger.LogBlock(FunctionId.DocumentState_ComputeChecksumsAsync, FilePath, cancellationToken)) { - var serializer = solutionServices.GetRequiredService(); - var infoChecksum = this.Attributes.Checksum; var serializableText = await SerializableSourceText.FromTextDocumentStateAsync(this, cancellationToken).ConfigureAwait(false); - var textChecksum = serializer.CreateChecksum(serializableText, cancellationToken); + var textChecksum = Checksum.Create(serializableText.ContentHash); return new DocumentStateChecksums(this.Id, infoChecksum, textChecksum); } From d9e1914b9d148c8fe5800e73faa675a3075a9e96 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 13:43:30 -0700 Subject: [PATCH 088/292] Rename --- .../Serialization/SerializableSourceText.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 3ef976b2aec21..aa83e25aa36c1 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -115,7 +115,7 @@ public static ValueTask FromTextDocumentStateAsync( if (state.TextAndVersionSource.TextLoader is SerializableSourceTextLoader serializableLoader) { // If we're already pointing at a serializable loader, we can just use that directly. - return new(serializableLoader.Text); + return new(serializableLoader.SerializableSourceText); } else if (state.Storage is TemporaryTextStorage storage) { @@ -213,23 +213,23 @@ public TextLoader ToTextLoader(string? filePath) /// private sealed class SerializableSourceTextLoader : TextLoader { - public readonly SerializableSourceText Text; + public readonly SerializableSourceText SerializableSourceText; private readonly AsyncLazy _lazyTextAndVersion; public SerializableSourceTextLoader( - SerializableSourceText text, + SerializableSourceText serializableSourceText, string? filePath) { - Text = text; + SerializableSourceText = serializableSourceText; var version = VersionStamp.Create(); this.FilePath = filePath; _lazyTextAndVersion = AsyncLazy.Create( async static (tuple, cancellationToken) => - TextAndVersion.Create(await tuple.text.GetTextAsync(cancellationToken).ConfigureAwait(false), tuple.version, tuple.filePath), + TextAndVersion.Create(await tuple.serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false), tuple.version, tuple.filePath), static (tuple, cancellationToken) => - TextAndVersion.Create(tuple.text.GetText(cancellationToken), tuple.version, tuple.filePath), - (text, version, filePath)); + TextAndVersion.Create(tuple.serializableSourceText.GetText(cancellationToken), tuple.version, tuple.filePath), + (serializableSourceText, version, filePath)); } internal override string? FilePath { get; } From dd3177e009074ad03709609558a92ac35858271a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 13:59:31 -0700 Subject: [PATCH 089/292] Add back --- .../Core/Portable/Serialization/SerializableSourceText.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index aa83e25aa36c1..1293fe1eb62a0 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -14,6 +14,10 @@ using Roslyn.Utilities; using static Microsoft.CodeAnalysis.Host.TemporaryStorageService; +#if DEBUG +using System.Linq; +#endif + namespace Microsoft.CodeAnalysis.Serialization; #pragma warning disable CA1416 // Validate platform compatibility From fd77028539c5f8d4872fa29dc61af59e69fd6570 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 14:07:45 -0700 Subject: [PATCH 090/292] Simplify --- .../Portable/Serialization/SerializableSourceText.cs | 9 +++++++-- .../Core/Portable/Serialization/SerializerService.cs | 2 +- .../Workspace/Solution/TextDocumentState_Checksum.cs | 2 +- .../RemoteAssetSynchronizationService.cs | 5 +---- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 1293fe1eb62a0..0a7bcf15f227c 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -49,7 +49,7 @@ internal sealed class SerializableSourceText /// The hash that would be produced by calling on . Can be passed in when already known to avoid unnecessary computation costs. /// - public readonly ImmutableArray ContentHash; + private readonly ImmutableArray _contentHash; /// /// Weak reference to a SourceText computed from . Useful so that if multiple requests @@ -57,6 +57,11 @@ internal sealed class SerializableSourceText /// private readonly WeakReference _computedText = new(target: null); + /// + /// Checksum of the contents (see ) of the text. + /// + public Checksum ContentChecksum => Checksum.Create(_contentHash); + public SerializableSourceText(TemporaryTextStorage storage, ImmutableArray contentHash) : this(storage, text: null, contentHash) { @@ -73,7 +78,7 @@ private SerializableSourceText(TemporaryTextStorage? storage, SourceText? text, _storage = storage; _text = text; - ContentHash = contentHash; + _contentHash = contentHash; #if DEBUG var computedContentHash = TryGetText()?.GetContentHash() ?? _storage!.ContentHash; diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 9d94bccc485e8..e263fdad2c23b 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -84,7 +84,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken return CreateChecksum((AnalyzerReference)value, cancellationToken); case WellKnownSynchronizationKind.SerializableSourceText: - return Checksum.Create(((SerializableSourceText)value).ContentHash); + throw new InvalidOperationException("Clients can already get a checksum directly from a SerializableSourceText"); default: // object that is not part of solution is not supported since we don't know what inputs are required to diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs index d886e011e9112..3f646f09e1971 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState_Checksum.cs @@ -38,7 +38,7 @@ private async Task ComputeChecksumsAsync(CancellationTok { var infoChecksum = this.Attributes.Checksum; var serializableText = await SerializableSourceText.FromTextDocumentStateAsync(this, cancellationToken).ConfigureAwait(false); - var textChecksum = Checksum.Create(serializableText.ContentHash); + var textChecksum = serializableText.ContentChecksum; return new DocumentStateChecksums(this.Id, infoChecksum, textChecksum); } diff --git a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs index f46c290d40e5d..c86c7482b169d 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/AssetSynchronization/RemoteAssetSynchronizationService.cs @@ -56,8 +56,6 @@ public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextCh using (RoslynLogger.LogBlock(FunctionId.RemoteHostService_SynchronizeTextAsync, Checksum.GetChecksumLogInfo, baseTextChecksum, cancellationToken)) { - var serializer = workspace.Services.GetRequiredService(); - // Try to get the text associated with baseTextChecksum var text = await TryGetSourceTextAsync(WorkspaceManager, workspace, documentId, baseTextChecksum, cancellationToken).ConfigureAwait(false); if (text == null) @@ -72,9 +70,8 @@ public ValueTask SynchronizeTextAsync(DocumentId documentId, Checksum baseTextCh // the entire document. var newText = text.WithChanges(textChanges); var newSerializableText = new SerializableSourceText(newText, newText.GetContentHash()); - var newChecksum = serializer.CreateChecksum(newSerializableText, cancellationToken); - WorkspaceManager.SolutionAssetCache.GetOrAdd(newChecksum, newSerializableText); + WorkspaceManager.SolutionAssetCache.GetOrAdd(newSerializableText.ContentChecksum, newSerializableText); } return; From 07ee0837209b65cefdf67e7cf31204ccf9ba2598 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 14:12:18 -0700 Subject: [PATCH 091/292] test checksums --- .../Core/Test.Next/Remote/SerializationValidator.cs | 2 +- .../Core/Test.Next/Remote/SnapshotSerializationTests.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index 77cd8df2ccb65..cf37f02b4b2ae 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -189,7 +189,7 @@ internal async Task VerifyAssetAsync(Checksum attributeChecksum, Checksum textCh await VerifyAssetSerializationAsync( textChecksum, WellKnownSynchronizationKind.SerializableSourceText, - (v, k, s) => new SolutionAsset(s.CreateChecksum(v, CancellationToken.None), v)); + (v, k, s) => new SolutionAsset(v.ContentChecksum, v)); } internal async Task VerifyAssetSerializationAsync( diff --git a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs index bbb5f577a5433..e06ebecbeef94 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs @@ -536,7 +536,7 @@ public async Task EmptyAssetChecksumTest() var serializer = document.Project.Solution.Services.GetService(); var text = await document.GetTextAsync().ConfigureAwait(false); - var source = serializer.CreateChecksum(new SerializableSourceText(text, text.GetContentHash()), CancellationToken.None); + var source = new SerializableSourceText(text, text.GetContentHash()).ContentChecksum; var metadata = serializer.CreateChecksum(new MissingMetadataReference(), CancellationToken.None); var analyzer = serializer.CreateChecksum(new AnalyzerFileReference(Path.Combine(TempRoot.Root, "missing"), new MissingAnalyzerLoader()), CancellationToken.None); @@ -693,7 +693,9 @@ private static SolutionAsset CloneAsset(ISerializerService serializer, SolutionA stream.Position = 0; using var reader = ObjectReader.TryGetReader(stream); var recovered = serializer.Deserialize(asset.Kind, reader, CancellationToken.None); - var assetFromStorage = new SolutionAsset(serializer.CreateChecksum(recovered, CancellationToken.None), recovered); + var checksum = recovered is SerializableSourceText text ? text.ContentChecksum : serializer.CreateChecksum(recovered, CancellationToken.None); + + var assetFromStorage = new SolutionAsset(checksum, recovered); Assert.Equal(asset.Checksum, assetFromStorage.Checksum); return assetFromStorage; From 9a1bbe2e0a74b8c9985243076c2c672d62bd0223 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Thu, 18 Apr 2024 14:33:37 -0700 Subject: [PATCH 092/292] Implement XHR IDiagnosticSource --- .../Protocol/Extensions/Extensions.cs | 2 - .../PublicDocumentPullDiagnosticsHandler.cs | 2 - .../Contracts/HotReloadDocumentDiagnostics.cs | 14 +++ .../Contracts/IHotReloadDiagnosticManager.cs | 28 ++++++ .../Contracts/IHotReloadDiagnosticService.cs | 18 ---- ...stractHotReloadDiagnosticSourceProvider.cs | 24 +++++ ...cumentHotReloadDiagnosticSourceProvider.cs | 39 ++++++++ .../Internal/HotReloadDiagnosticManager.cs | 95 +++++++++++++++++++ .../Internal/HotReloadDiagnosticService.cs | 29 ------ .../Internal/HotReloadDiagnosticSource.cs | 28 +++--- .../HotReloadDiagnosticSourceProvider.cs | 55 ----------- ...kspaceHotReloadDiagnosticSourceProvider.cs | 47 +++++++++ .../InternalAPI.Unshipped.txt | 31 ++++-- 13 files changed, 284 insertions(+), 128 deletions(-) create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index 890759ebb9ceb..d9b8e4078f89f 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -10,8 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 15e6062a31754..9e1f3cfdd33c7 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -23,7 +23,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [Method(Methods.TextDocumentDiagnosticName)] internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler { - private readonly string _nonLocalDiagnosticsSourceRegistrationId; private readonly IClientLanguageServerManager _clientLanguageServerManager; private readonly IDiagnosticSourceManager _diagnosticSourceManager; @@ -35,7 +34,6 @@ public PublicDocumentPullDiagnosticsHandler( IGlobalOptionService globalOptions) : base(analyzerService, diagnosticsRefresher, globalOptions) { - _nonLocalDiagnosticsSourceRegistrationId = Guid.NewGuid().ToString(); _diagnosticSourceManager = diagnosticSourceManager; _clientLanguageServerManager = clientLanguageServerManager; } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs new file mode 100644 index 0000000000000..6f948ddfaef3d --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +{ + internal class HotReloadDocumentDiagnostics(DocumentId documentId, ImmutableArray errors) + { + public DocumentId DocumentId => documentId; + public ImmutableArray Errors => errors; + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs new file mode 100644 index 0000000000000..aa48b2ea23197 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +{ + internal interface IHotReloadDiagnosticManager + { + /// + /// Hot reload errors. + /// + ImmutableArray Errors { get; } + + /// + /// Update the diagnostics for the given group name. + /// + /// The diagnostics. + /// The group name. + void UpdateErrors(ImmutableArray errors, string groupName); + + /// + /// Clears all errors. + /// + void Clear(); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs deleted file mode 100644 index 9e55d78ba20e3..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticService.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts -{ - internal interface IHotReloadDiagnosticService - { - /// - /// Update the diagnostics for the given group name. - /// - /// The diagnostics. - /// The group name. - void UpdateDiagnostics(IEnumerable diagnostics, string groupName); - } -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..7c6a0e6915c9d --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +internal class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider +{ + internal const string SourceName = "HotReloadDiagnostic"; + internal static readonly ImmutableArray SourceNames = [SourceName]; + + ImmutableArray IDiagnosticSourceProvider.SourceNames => SourceNames; + bool IDiagnosticSourceProvider.IsDocument => throw new NotImplementedException(); + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + => throw new NotImplementedException(); + +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..46f3f36252622 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadErrorService) + : AbstractHotReloadDiagnosticSourceProvider + , IDiagnosticSourceProvider +{ + bool IDiagnosticSourceProvider.IsDocument => true; + + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } textDocument) + { + if (hotReloadErrorService.Errors.FirstOrDefault(e => e.DocumentId == textDocument.Id) is { } documentErrors) + { + return new([new HotReloadDiagnosticSource(textDocument, documentErrors.Errors)]); + } + } + + return new([]); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs new file mode 100644 index 0000000000000..14b54af628827 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using System.Collections.Generic; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +[Export(typeof(IHotReloadDiagnosticManager)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class HotReloadDiagnosticManager(IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager +{ + private ImmutableDictionary> _errors = ImmutableDictionary>.Empty; + private ImmutableArray? _allErrors = null; + + void IHotReloadDiagnosticManager.UpdateErrors(ImmutableArray errors, string groupName) + { + errors = errors.RemoveAll(d => d.Errors.IsEmpty); + + var oldErrors = _errors; + if (errors.IsEmpty) + { + _errors = _errors.Remove(groupName); + } + else + { + _errors = _errors.SetItem(groupName, errors); + } + + if (_errors != oldErrors) + { + _allErrors = null; + diagnosticsRefresher.RequestWorkspaceRefresh(); + } + } + + void IHotReloadDiagnosticManager.Clear() + { + if (!_errors.IsEmpty) + { + _errors = ImmutableDictionary>.Empty; + _allErrors = ImmutableArray.Empty; + diagnosticsRefresher.RequestWorkspaceRefresh(); + } + } + + ImmutableArray IHotReloadDiagnosticManager.Errors + { + get + { + _allErrors ??= ComputeAllErrors(_errors); + return _allErrors.Value; + } + } + + private static ImmutableArray ComputeAllErrors(ImmutableDictionary> errors) + { + if (errors.Count == 0) + { + return ImmutableArray.Empty; + } + + if (errors.Count == 1) + { + return errors.First().Value; + } + + var allErrors = new Dictionary>(); + foreach (var group in errors.Values) + { + foreach (var documentErrors in group) + { + if (!allErrors.TryGetValue(documentErrors.DocumentId, out var list)) + { + list = new List(); + allErrors.Add(documentErrors.DocumentId, list); + } + + list.AddRange(documentErrors.Errors); + } + } + + return allErrors + .Where(kvp => kvp.Value.Count > 0) + .Select(kvp => new HotReloadDocumentDiagnostics(kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableArray(); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs deleted file mode 100644 index 6b8b08db21c92..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticService.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Composition; -using System.Linq; -using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -[Export(typeof(IHotReloadDiagnosticService)), Shared] -internal sealed class HotReloadDiagnosticService : IHotReloadDiagnosticService -{ - private readonly IHotReloadDiagnosticService? _implementation; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public HotReloadDiagnosticService([ImportMany] IEnumerable sourceProviders) - { - _implementation = sourceProviders.OfType().FirstOrDefault(); - } - - void IHotReloadDiagnosticService.UpdateDiagnostics(IEnumerable diagnostics, string groupName) - => _implementation?.UpdateDiagnostics(diagnostics, groupName); -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs index 94cc82c6277f5..c11487fd93070 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -5,26 +5,30 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Text; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; using Roslyn.LanguageServer.Protocol; +using Microsoft.CodeAnalysis.LanguageServer; -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -internal sealed class HotReloadDiagnosticSource(Project project, IDiagnosticsRefresher diagnosticsRefresher) : IDiagnosticSource +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal { - Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + internal class HotReloadDiagnosticSource(TextDocument document, ImmutableArray errors) : IDiagnosticSource { - throw new NotImplementedException(); - } + Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + var result = errors.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); + return Task.FromResult(result); + } - TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => null; - ProjectOrDocumentId IDiagnosticSource.GetId() => new(project.Id); - Project IDiagnosticSource.GetProject() => project; - bool IDiagnosticSource.IsLiveSource() => true; - string IDiagnosticSource.ToDisplayString() => project.Name; + TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; + ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id); + Project IDiagnosticSource.GetProject() => document.Project; + bool IDiagnosticSource.IsLiveSource() => true; + string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {document.FilePath ?? document.Name} in {document.Project.Name}"; + } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs deleted file mode 100644 index 7a575e5885cd7..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; -using System.Collections.Generic; -using System.Collections.Concurrent; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -// THIS IS WRONG. Need to follow EdinAndContinue -[ExportDiagnosticSourceProvider, Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class HotReloadDiagnosticSourceProvider(IDiagnosticsRefresher diagnosticsRefresher) - : IDiagnosticSourceProvider - , IHotReloadDiagnosticService -{ - private const string SourceName = "HotReloadDiagnostic"; - private static readonly ImmutableArray sourceNames = [SourceName]; - - private readonly ConcurrentDictionary projectDiagnostics = new(); - - bool IDiagnosticSourceProvider.IsDocument => false; - ImmutableArray IDiagnosticSourceProvider.SourceNames => sourceNames; - - void IHotReloadDiagnosticService.UpdateDiagnostics(IEnumerable diagnostics, string sourceName) - { - // TODO: store diagnostics in projectDiagnostics - //foreach (var diagnostic in diagnostics) - //{ - //} - diagnosticsRefresher.RequestWorkspaceRefresh(); - } - - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) - { - if (sourceName == SourceName) - { - return new(projectDiagnostics.Values.ToImmutableArray()); - } - - return new([]); - } -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..defca19ba4f9b --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadErrorService) + : AbstractHotReloadDiagnosticSourceProvider + , IDiagnosticSourceProvider +{ + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + { + if (sourceName != SourceName || context.Solution is not Solution solution) + { + return new([]); + } + + using var _ = ArrayBuilder.GetInstance(out var builder); + foreach (var documentErrors in hotReloadErrorService.Errors) + { + TextDocument? document = solution.GetAdditionalDocument(documentErrors.DocumentId) ?? solution.GetDocument(documentErrors.DocumentId); + if (document != null && !context.IsTracking(document.GetURI())) + { + builder.Add(new HotReloadDiagnosticSource(document, documentErrors.Errors)); + } + } + + var result = builder.ToImmutableAndClear(); + return new(result); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 8460dac45da33..9deff7bbdd06d 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,20 +1,31 @@ -const Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource.SourceName = "XamlHotReloadDiagnostics" -> string! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticService -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticService.UpdateDiagnostics(System.Collections.Generic.IEnumerable! diagnostics, string! groupName) -> void +const Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.SourceName = "HotReloadDiagnostic" -> string! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.DocumentId.get -> Microsoft.CodeAnalysis.DocumentId! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.Errors.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.HotReloadDocumentDiagnostics(Microsoft.CodeAnalysis.DocumentId! documentId, System.Collections.Immutable.ImmutableArray errors) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Clear() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Errors.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.UpdateErrors(System.Collections.Immutable.ImmutableArray errors, string! groupName) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStartAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStopAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.InitializeAsync(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.RequestDataBridgeConnectionAsync(string! connectionId, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticService -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticService.HotReloadDiagnosticService(System.Collections.Generic.IEnumerable! sourceProviders) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.HotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.XamlHotReloadDiagnosticSource.XamlHotReloadDiagnosticSource(Microsoft.CodeAnalysis.Project! project, Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.AbstractHotReloadDiagnosticSourceProvider() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadErrorService) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.HotReloadDiagnosticManager(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.TextDocument! document, System.Collections.Immutable.ImmutableArray errors) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadErrorService) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry.RunningProcessEntry() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.CreateILspService(Microsoft.CodeAnalysis.LanguageServer.LspServices! lspServices, Microsoft.CodeAnalysis.LanguageServer.WellKnownLspServerKinds serverKind) -> Microsoft.CodeAnalysis.LanguageServer.ILspService! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.OnServiceBrokerInitialized(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.VisualDiagnosticsServiceFactory(Microsoft.CodeAnalysis.LanguageServer.LspWorkspaceRegistrationService! lspWorkspaceRegistrationService) -> void \ No newline at end of file +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.VisualDiagnosticsServiceFactory(Microsoft.CodeAnalysis.LanguageServer.LspWorkspaceRegistrationService! lspWorkspaceRegistrationService) -> void +static readonly Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.SourceNames -> System.Collections.Immutable.ImmutableArray \ No newline at end of file From 0e493b6fb12f6b5c14853e8f68947208edb47d47 Mon Sep 17 00:00:00 2001 From: Maria Solano Date: Thu, 18 Apr 2024 14:35:26 -0700 Subject: [PATCH 093/292] Hook up TS pull diagnostics with todos --- .../VSTypeScript/VSTypeScriptInProcLanguageClient.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs index 78d641cebbfc2..6ad9d1bf4f913 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.Composition; using Microsoft.VisualStudio.LanguageServer.Client; @@ -57,6 +58,15 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.ProjectContextProvider = true; serverCapabilities.SupportsDiagnosticRequests = true; + serverCapabilities.DiagnosticProvider = new() + { + SupportsMultipleContextsDiagnostics = true, + DiagnosticKinds = + [ + new(PullDiagnosticCategories.Task), + ] + }; + return serverCapabilities; } From f8462e7f1b1a9a4a014ebc4b1ca7449c0bb7786e Mon Sep 17 00:00:00 2001 From: Maria Solano Date: Thu, 18 Apr 2024 14:54:41 -0700 Subject: [PATCH 094/292] Add the other kinds --- .../VSTypeScript/VSTypeScriptInProcLanguageClient.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs index 6ad9d1bf4f913..be675274d5530 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs @@ -64,6 +64,12 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa DiagnosticKinds = [ new(PullDiagnosticCategories.Task), + new(PullDiagnosticCategories.EditAndContinue), + new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), + new(PullDiagnosticCategories.DocumentCompilerSyntax), + new(PullDiagnosticCategories.DocumentCompilerSemantic), + new(PullDiagnosticCategories.DocumentAnalyzerSyntax), + new(PullDiagnosticCategories.DocumentAnalyzerSemantic), ] }; From d9229a70862181e38516914958c79c8250c13de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Thu, 18 Apr 2024 16:14:07 -0700 Subject: [PATCH 095/292] Allow WatchHotReloadService to change capabilities during session (#73066) --- .../Watch/Api/WatchHotReloadService.cs | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index d13938a57c7ad..d0762770efc95 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Immutable; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; @@ -14,12 +13,10 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Watch.Api; -internal sealed class WatchHotReloadService +internal sealed class WatchHotReloadService(SolutionServices services, Func>> capabilitiesProvider) { - private sealed class DebuggerService(ImmutableArray capabilities) : IManagedHotReloadService + private sealed class DebuggerService(Func>> capabilitiesProvider) : IManagedHotReloadService { - private readonly ImmutableArray _capabilities = capabilities; - public ValueTask> GetActiveStatementsAsync(CancellationToken cancellationToken) => ValueTaskFactory.FromResult(ImmutableArray.Empty); @@ -27,41 +24,39 @@ public ValueTask GetAvailabilityAsync(Guid module, => ValueTaskFactory.FromResult(new ManagedHotReloadAvailability(ManagedHotReloadAvailabilityStatus.Available)); public ValueTask> GetCapabilitiesAsync(CancellationToken cancellationToken) - => ValueTaskFactory.FromResult(_capabilities); + => capabilitiesProvider(); public ValueTask PrepareModuleForUpdateAsync(Guid module, CancellationToken cancellationToken) => ValueTaskFactory.CompletedTask; } - public readonly struct Update + public readonly struct Update( + Guid moduleId, + ImmutableArray ilDelta, + ImmutableArray metadataDelta, + ImmutableArray pdbDelta, + ImmutableArray updatedTypes, + ImmutableArray requiredCapabilities) { - public readonly Guid ModuleId; - public readonly ImmutableArray ILDelta; - public readonly ImmutableArray MetadataDelta; - public readonly ImmutableArray PdbDelta; - public readonly ImmutableArray UpdatedTypes; - public readonly ImmutableArray RequiredCapabilities; - - internal Update(Guid moduleId, ImmutableArray ilDelta, ImmutableArray metadataDelta, ImmutableArray pdbDelta, ImmutableArray updatedTypes, ImmutableArray requiredCapabilities) - { - ModuleId = moduleId; - ILDelta = ilDelta; - MetadataDelta = metadataDelta; - PdbDelta = pdbDelta; - UpdatedTypes = updatedTypes; - RequiredCapabilities = requiredCapabilities; - } + public readonly Guid ModuleId = moduleId; + public readonly ImmutableArray ILDelta = ilDelta; + public readonly ImmutableArray MetadataDelta = metadataDelta; + public readonly ImmutableArray PdbDelta = pdbDelta; + public readonly ImmutableArray UpdatedTypes = updatedTypes; + public readonly ImmutableArray RequiredCapabilities = requiredCapabilities; } private static readonly ActiveStatementSpanProvider s_solutionActiveStatementSpanProvider = (_, _, _) => ValueTaskFactory.FromResult(ImmutableArray.Empty); - private readonly IEditAndContinueService _encService; + private readonly IEditAndContinueService _encService = services.GetRequiredService().Service; + private DebuggingSessionId _sessionId; - private readonly ImmutableArray _capabilities; public WatchHotReloadService(HostWorkspaceServices services, ImmutableArray capabilities) - => (_encService, _capabilities) = (services.GetRequiredService().Service, capabilities); + : this(services.SolutionServices, () => ValueTaskFactory.FromResult(capabilities)) + { + } /// /// Starts the watcher. @@ -72,7 +67,7 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell { var newSessionId = await _encService.StartDebuggingSessionAsync( solution, - new DebuggerService(_capabilities), + new DebuggerService(capabilitiesProvider), NullPdbMatchingSourceTextProvider.Instance, captureMatchingDocuments: [], captureAllMatchingDocuments: true, @@ -82,6 +77,17 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell _sessionId = newSessionId; } + /// + /// Invoke when capabilities have changed. + /// + public void CapabilitiesChanged() + { + var sessionId = _sessionId; + Contract.ThrowIfFalse(sessionId != default, "Session has not started"); + + _encService.BreakStateOrCapabilitiesChanged(sessionId, inBreakState: null); + } + /// /// Emits updates for all projects that differ between the given snapshot and the one given to the previous successful call or /// the one passed to for the first invocation. From 70351ce2a5eb3823baee495e330d9d651412cca3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 14:33:20 -0700 Subject: [PATCH 096/292] Do not dump --- .../Portable/Serialization/SerializableSourceText.cs | 9 +++++++++ .../Portable/Workspace/Solution/TextDocumentState.cs | 4 +++- .../Core/Portable/Workspace/Solution/TextLoader.cs | 10 ++++++++++ .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 9 ++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 0a7bcf15f227c..5b047c712cf1b 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -243,6 +243,15 @@ async static (tuple, cancellationToken) => internal override string? FilePath { get; } + /// + /// Documents should always hold onto instances of this text loader strongly. In other words, they should load + /// from this, and then dump the contents into a RecoverableText object that then dumps the contents to a memory + /// mapped file within this process. Doing that is pointless as the contents of this text are already in a + /// memory mapped file on the host side. + /// + internal override bool AlwaysHoldStrongly + => true; + public override Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) => _lazyTextAndVersion.GetValueAsync(cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 33a799e996aba..0a8bbd9ffb828 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -60,7 +60,9 @@ public TextDocumentState(SolutionServices solutionServices, DocumentInfo info, L info.DocumentServiceProvider, info.Attributes, textAndVersionSource: info.TextLoader != null - ? CreateRecoverableText(info.TextLoader, solutionServices) + ? info.TextLoader.AlwaysHoldStrongly + ? CreateStrongText(info.TextLoader) + : CreateRecoverableText(info.TextLoader, solutionServices) : CreateStrongText(TextAndVersion.Create(SourceText.From(string.Empty, encoding: null, loadTextOptions.ChecksumAlgorithm), VersionStamp.Default, info.FilePath)), loadTextOptions) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs index e771796d35fac..9b4ab7c3e2d86 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs @@ -31,6 +31,16 @@ public abstract class TextLoader internal virtual string? FilePath => null; + /// + /// if the document that holds onto this loader should do so with a strong reference, versus + /// a reference that will take the contents of this loader and store them in a recoverable form. This should be + /// used when the underlying data is already stored in a recoverable form somewhere else and it would be wasteful to + /// store another copy. For example, a document that is backed by memory-mapped contents in another process does not + /// need to dump it's content to another memory-mapped file in the process it lives in. It can always recover the + /// text from the original process. + /// + internal virtual bool AlwaysHoldStrongly => false; + /// /// True if reloads from its original binary representation (e.g. file on disk). /// diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 5a44050959030..9bf1cccabf9c4 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -618,7 +618,14 @@ private async Task UpdateDocumentAsync( var serializableSourceText = await _assetProvider.GetAssetAsync( assetPath: document.Id, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); var loader = serializableSourceText.ToTextLoader(document.FilePath); - var mode = PreservationMode.PreserveValue; + + // We strongly want to use identity-preservation with these loaders. By doing that we'll always use + // this loader as the 'truth' of how to get teh source-text for a particular document. When the + // text is first needed it will be pulled from the memory mapped files on the host side. Then, if + // that text is ever dropped, we'll still be pointing at this same loader. That will ensure that we + // still read the data from the host side, without doing something silly (like dumping it to our own + // memory mapped data on the OOP side). + var mode = PreservationMode.PreserveIdentity; document = document.Kind switch { From e97e662dec65af3b81be88ef5ed416701ffb50d2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 14:35:12 -0700 Subject: [PATCH 097/292] Defer loading more --- .../Serialization/SerializableSourceText.cs | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 5b047c712cf1b..14d89068abbb2 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -220,28 +220,14 @@ public TextLoader ToTextLoader(string? filePath) /// simply point to the segments in the memory-mapped-file the host has dumped its text into, and only actually /// realizing the real text values when they're needed. /// - private sealed class SerializableSourceTextLoader : TextLoader + private sealed class SerializableSourceTextLoader( + SerializableSourceText serializableSourceText, + string? filePath) : TextLoader { - public readonly SerializableSourceText SerializableSourceText; - private readonly AsyncLazy _lazyTextAndVersion; + public readonly SerializableSourceText SerializableSourceText = serializableSourceText; + private readonly VersionStamp _version = VersionStamp.Create(); - public SerializableSourceTextLoader( - SerializableSourceText serializableSourceText, - string? filePath) - { - SerializableSourceText = serializableSourceText; - var version = VersionStamp.Create(); - - this.FilePath = filePath; - _lazyTextAndVersion = AsyncLazy.Create( - async static (tuple, cancellationToken) => - TextAndVersion.Create(await tuple.serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false), tuple.version, tuple.filePath), - static (tuple, cancellationToken) => - TextAndVersion.Create(tuple.serializableSourceText.GetText(cancellationToken), tuple.version, tuple.filePath), - (serializableSourceText, version, filePath)); - } - - internal override string? FilePath { get; } + internal override string? FilePath { get; } = filePath; /// /// Documents should always hold onto instances of this text loader strongly. In other words, they should load @@ -252,10 +238,10 @@ async static (tuple, cancellationToken) => internal override bool AlwaysHoldStrongly => true; - public override Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) - => _lazyTextAndVersion.GetValueAsync(cancellationToken); + public override async Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) + => TextAndVersion.Create(await this.SerializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false), _version); internal override TextAndVersion LoadTextAndVersionSynchronously(LoadTextOptions options, CancellationToken cancellationToken) - => _lazyTextAndVersion.GetValue(cancellationToken); + => TextAndVersion.Create(this.SerializableSourceText.GetText(cancellationToken), _version); } } From 333c9660a0d0c476a068b9dc0b9b07a97741064f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 14:35:49 -0700 Subject: [PATCH 098/292] normal constructor --- .../Serialization/SerializableSourceText.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 14d89068abbb2..de9b534aa51ab 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -220,14 +220,20 @@ public TextLoader ToTextLoader(string? filePath) /// simply point to the segments in the memory-mapped-file the host has dumped its text into, and only actually /// realizing the real text values when they're needed. /// - private sealed class SerializableSourceTextLoader( - SerializableSourceText serializableSourceText, - string? filePath) : TextLoader + private sealed class SerializableSourceTextLoader : TextLoader { - public readonly SerializableSourceText SerializableSourceText = serializableSourceText; + public readonly SerializableSourceText SerializableSourceText; private readonly VersionStamp _version = VersionStamp.Create(); - internal override string? FilePath { get; } = filePath; + public SerializableSourceTextLoader( + SerializableSourceText serializableSourceText, + string? filePath) + { + SerializableSourceText = serializableSourceText; + FilePath = filePath; + } + + internal override string? FilePath { get; } /// /// Documents should always hold onto instances of this text loader strongly. In other words, they should load From a14cfa0bdec8fb56f90042373a05e70f3f01fd01 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 15:12:20 -0700 Subject: [PATCH 099/292] no dump semantics --- .../Workspace/Solution/TextDocumentState.cs | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 0a8bbd9ffb828..1ec64287e0972 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -60,9 +60,7 @@ public TextDocumentState(SolutionServices solutionServices, DocumentInfo info, L info.DocumentServiceProvider, info.Attributes, textAndVersionSource: info.TextLoader != null - ? info.TextLoader.AlwaysHoldStrongly - ? CreateStrongText(info.TextLoader) - : CreateRecoverableText(info.TextLoader, solutionServices) + ? CreateTextFromLoader(info.TextLoader, PreservationMode.PreserveValue, solutionServices) : CreateStrongText(TextAndVersion.Create(SourceText.From(string.Empty, encoding: null, loadTextOptions.ChecksumAlgorithm), VersionStamp.Default, info.FilePath)), loadTextOptions) { @@ -76,9 +74,6 @@ public TextDocumentState(SolutionServices solutionServices, DocumentInfo info, L private static ITextAndVersionSource CreateStrongText(TextAndVersion text) => new ConstantTextAndVersionSource(text); - private static ITextAndVersionSource CreateStrongText(TextLoader loader) - => new LoadableTextAndVersionSource(loader, cacheResult: true); - private static ITextAndVersionSource CreateRecoverableText(TextAndVersion text, SolutionServices services) { var service = services.GetRequiredService(); @@ -89,16 +84,6 @@ private static ITextAndVersionSource CreateRecoverableText(TextAndVersion text, : new RecoverableTextAndVersion(new ConstantTextAndVersionSource(text), services); } - private static ITextAndVersionSource CreateRecoverableText(TextLoader loader, SolutionServices services) - { - var service = services.GetRequiredService(); - var options = service.Options; - - return options.DisableRecoverableText - ? CreateStrongText(loader) - : new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), services); - } - public ITemporaryTextStorageInternal? Storage => (TextAndVersionSource as RecoverableTextAndVersion)?.Storage; @@ -180,13 +165,21 @@ public TextDocumentState UpdateText(SourceText newText, PreservationMode mode) public TextDocumentState UpdateText(TextLoader loader, PreservationMode mode) { // don't blow up on non-text documents. - var newTextSource = mode == PreservationMode.PreserveIdentity - ? CreateStrongText(loader) - : CreateRecoverableText(loader, solutionServices); + var newTextSource = CreateTextFromLoader(loader, mode, this.solutionServices); return UpdateText(newTextSource, mode, incremental: false); } + private static ITextAndVersionSource CreateTextFromLoader(TextLoader loader, PreservationMode mode, SolutionServices solutionServices) + { + var service = solutionServices.GetRequiredService(); + var options = service.Options; + + return mode == PreservationMode.PreserveIdentity || loader.AlwaysHoldStrongly || options.DisableRecoverableText + ? new LoadableTextAndVersionSource(loader, cacheResult: true) + : new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), solutionServices); + } + protected virtual TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental) { return new TextDocumentState( From 1b188495072a32be7447e363a522258ba656f0d1 Mon Sep 17 00:00:00 2001 From: Maria Solano Date: Thu, 18 Apr 2024 17:02:31 -0700 Subject: [PATCH 100/292] Remove extra kinds --- .../VSTypeScript/VSTypeScriptInProcLanguageClient.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs index be675274d5530..cfc295a276a4d 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptInProcLanguageClient.cs @@ -64,10 +64,7 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa DiagnosticKinds = [ new(PullDiagnosticCategories.Task), - new(PullDiagnosticCategories.EditAndContinue), new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), - new(PullDiagnosticCategories.DocumentCompilerSyntax), - new(PullDiagnosticCategories.DocumentCompilerSemantic), new(PullDiagnosticCategories.DocumentAnalyzerSyntax), new(PullDiagnosticCategories.DocumentAnalyzerSemantic), ] From 6dbaf63b5e24a6d88072453cfa2b7373fa23b121 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Thu, 18 Apr 2024 17:07:12 -0700 Subject: [PATCH 101/292] Cleanup binary version of clasp now that consumers have moved over --- Roslyn.sln | 8 ---- eng/config/PublishData.json | 1 - ...uageServerProtocol.Framework.Binary.csproj | 30 ------------- .../ExampleLanguageServer.cs | 2 +- .../AbstractHandlerProvider.cs | 4 -- .../AbstractLanguageServer.cs | 43 +++++-------------- .../AbstractLspLogger.cs | 4 -- .../AbstractRequestContextFactory.cs | 4 -- .../AbstractRequestScope.cs | 5 --- .../AbstractTelemetryService.cs | 4 -- .../HandlerProvider.cs | 2 +- .../Handlers/InitializeHandler.cs | 4 -- .../Handlers/InitializedHandler.cs | 4 -- .../IHandlerProvider.cs | 25 ----------- .../IInitializeManager.cs | 5 --- .../ILifeCycleManager.cs | 4 -- .../ILspLogger.cs | 5 --- .../ILspServices.cs | 5 --- .../IMethodHandler.cs | 4 -- .../INotificationHandler.cs | 8 ---- .../IQueueItem.cs | 4 -- .../IRequestContextFactory.cs | 42 ------------------ .../IRequestExecutionQueue.cs | 4 -- .../IRequestHandler.cs | 9 ---- .../ITextDocumentIdentifierHandler.cs | 8 ---- .../LanguageServerConstants.cs | 4 -- .../LanguageServerEndpointAttribute.cs | 4 -- ...eServerProtocol.Framework.Shared.projitems | 3 -- .../QueueItem.cs | 20 ++------- .../RequestExecutionQueue.cs | 17 -------- .../RequestHandlerMetadata.cs | 5 --- .../RequestShutdownEventArgs.cs | 5 --- .../WrappedHandlerProvider.cs | 30 ------------- .../Protocol/RoslynLanguageServer.cs | 10 ++--- .../Setup/Roslyn.VisualStudio.Setup.csproj | 6 --- 35 files changed, 21 insertions(+), 321 deletions(-) delete mode 100644 src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj delete mode 100644 src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs delete mode 100644 src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs delete mode 100644 src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs diff --git a/Roslyn.sln b/Roslyn.sln index b54926c007207..848adc0f76206 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -547,8 +547,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Replay", "src\Tools\Replay\ EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CommonLanguageServerProtocol.Framework.Shared", "src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.shproj", "{64EADED3-4B5D-4431-BBE5-A4ABA1C38C00}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CommonLanguageServerProtocol.Framework.Binary", "src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework.Binary\Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj", "{730CADBA-701F-4722-9B6F-1FCC0DF2C95D}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests", "src\Compilers\CSharp\Test\Emit3\Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests.csproj", "{4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SemanticSearch", "SemanticSearch", "{52ABB0E4-C3A1-4897-B51B-18EDA83F5D20}" @@ -1365,10 +1363,6 @@ Global {DB96C25F-39A9-4A6A-92BC-D1E42717308F}.Debug|Any CPU.Build.0 = Debug|Any CPU {DB96C25F-39A9-4A6A-92BC-D1E42717308F}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB96C25F-39A9-4A6A-92BC-D1E42717308F}.Release|Any CPU.Build.0 = Release|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D}.Release|Any CPU.Build.0 = Release|Any CPU {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1636,7 +1630,6 @@ Global {6D819E80-BA2F-4317-8368-37F8F4434D3A} = {8977A560-45C2-4EC2-A849-97335B382C74} {DB96C25F-39A9-4A6A-92BC-D1E42717308F} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC} {64EADED3-4B5D-4431-BBE5-A4ABA1C38C00} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} - {730CADBA-701F-4722-9B6F-1FCC0DF2C95D} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} {4E273CBC-BB1D-4AC1-91DB-C62FC83E0350} = {32A48625-F0AD-419D-828B-A50BDABA38EA} {52ABB0E4-C3A1-4897-B51B-18EDA83F5D20} = {FD0FAF5F-1DED-485C-99FA-84B97F3A8EEC} {FCE88BBD-9BBD-4871-B9B0-DE176D73A6B0} = {52ABB0E4-C3A1-4897-B51B-18EDA83F5D20} @@ -1686,7 +1679,6 @@ Global src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{64eaded3-4b5d-4431-bbe5-a4aba1c38c00}*SharedItemsImports = 13 src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{686bf57e-a6ff-467b-aab3-44de916a9772}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{699fea05-aea7-403d-827e-53cf4e826955}*SharedItemsImports = 13 - src\Features\LanguageServer\Microsoft.CommonLanguageServerProtocol.Framework\Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems*{730cadba-701f-4722-9b6f-1fcc0df2c95d}*SharedItemsImports = 5 src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{76242a2d-2600-49dd-8c15-fea07ecb1843}*SharedItemsImports = 5 src\Analyzers\Core\Analyzers\Analyzers.projitems*{76e96966-4780-4040-8197-bde2879516f4}*SharedItemsImports = 13 src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{7b7f4153-ae93-4908-b8f0-430871589f83}*SharedItemsImports = 13 diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index 6c568c4301a1c..ee01121b84fd6 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -89,7 +89,6 @@ "Microsoft.VisualStudio.LanguageServices.LiveShare": "vs-impl", "Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient": "vs-impl", "Microsoft.CommonLanguageServerProtocol.Framework": "vs-impl", - "Microsoft.CommonLanguageServerProtocol.Framework.Binary": "vs-impl" } }, "comment-about-servicing-branches": "For a list of VS versions under servicing, see https://docs.microsoft.com/en-us/visualstudio/releases/2019/servicing#support-options-for-enterprise-and-professional-customers", diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj deleted file mode 100644 index b82d6a2a340bd..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Binary/Microsoft.CommonLanguageServerProtocol.Framework.Binary.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - - Library - netstandard2.0 - Microsoft.CommonLanguageServerProtocol.Framework - - - true - Microsoft.CommonLanguageServerProtocol.Framework.Binary - - A legacy binary implementation of Microsoft.CommonLanguageServerProtocol.Framework. - - - - BINARY_COMPAT - - - - - - - diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs index eb1381272b95e..4d6774ecd3ab3 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework.Example/ExampleLanguageServer.cs @@ -27,7 +27,7 @@ protected override ILspServices ConstructLspServices() var serviceCollection = new ServiceCollection(); var _ = AddHandlers(serviceCollection) - .AddSingleton(_logger) + .AddSingleton(Logger) .AddSingleton, ExampleRequestContextFactory>() .AddSingleton(s => HandlerProvider) .AddSingleton, CapabilitiesManager>() diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs index 4af41c4bd96bc..7e36101ec4d64 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractHandlerProvider.cs @@ -13,11 +13,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// Manages handler discovery and distribution. /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractHandlerProvider -#else internal abstract class AbstractHandlerProvider -#endif { /// /// Gets the s for all registered methods. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs index f140ff52b78ee..8dddabcf31dbe 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs @@ -18,16 +18,10 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractLanguageServer -#else internal abstract class AbstractLanguageServer -#endif { private readonly JsonRpc _jsonRpc; -#pragma warning disable IDE1006 // Naming Styles - Required for API compat, TODO - https://github.com/dotnet/roslyn/issues/72251 - protected readonly ILspLogger _logger; -#pragma warning restore IDE1006 // Naming Styles + protected readonly ILspLogger Logger; protected readonly JsonSerializer _jsonSerializer; @@ -67,7 +61,7 @@ protected AbstractLanguageServer( JsonSerializer jsonSerializer, ILspLogger logger) { - _logger = logger; + Logger = logger; _jsonRpc = jsonRpc; _jsonSerializer = jsonSerializer; @@ -93,35 +87,20 @@ public void Initialize() /// This should only be called once, and then cached. protected abstract ILspServices ConstructLspServices(); - [Obsolete($"Use {nameof(HandlerProvider)} property instead.", error: false)] - protected virtual IHandlerProvider GetHandlerProvider() - { - var lspServices = _lspServices.Value; - var handlerProvider = new HandlerProvider(lspServices); - SetupRequestDispatcher(handlerProvider); - - return handlerProvider; - } - protected virtual AbstractHandlerProvider HandlerProvider { get { -#pragma warning disable CS0618 // Type or member is obsolete - var handlerProvider = GetHandlerProvider(); -#pragma warning restore CS0618 // Type or member is obsolete - if (handlerProvider is AbstractHandlerProvider abstractHandlerProvider) - { - return abstractHandlerProvider; - } - - return new WrappedHandlerProvider(handlerProvider); + var lspServices = _lspServices.Value; + var handlerProvider = new HandlerProvider(lspServices); + SetupRequestDispatcher(handlerProvider); + return handlerProvider; } } public ILspServices GetLspServices() => _lspServices.Value; - protected virtual void SetupRequestDispatcher(IHandlerProvider handlerProvider) + protected virtual void SetupRequestDispatcher(AbstractHandlerProvider handlerProvider) { var entryPointMethodInfo = typeof(DelegatingEntryPoint).GetMethod(nameof(DelegatingEntryPoint.ExecuteRequestAsync))!; // Get unique set of methods from the handler provider for the default language. @@ -187,7 +166,7 @@ public virtual void OnInitialized() protected virtual IRequestExecutionQueue ConstructRequestExecutionQueue() { var handlerProvider = HandlerProvider; - var queue = new RequestExecutionQueue(this, _logger, handlerProvider); + var queue = new RequestExecutionQueue(this, Logger, handlerProvider); queue.Start(); @@ -201,7 +180,7 @@ protected IRequestExecutionQueue GetRequestExecutionQueue() protected virtual string GetLanguageForRequest(string methodName, JToken? parameters) { - _logger.LogInformation($"Using default language handler for {methodName}"); + Logger.LogInformation($"Using default language handler for {methodName}"); return LanguageServerConstants.DefaultLanguageName; } @@ -328,7 +307,7 @@ async Task Shutdown_NoLockAsync(string message) // Immediately yield so that this does not run under the lock. await Task.Yield(); - _logger.LogInformation(message); + Logger.LogInformation(message); // Allow implementations to do any additional cleanup on shutdown. var lifeCycleManager = GetLspServices().GetRequiredService(); @@ -386,7 +365,7 @@ async Task Exit_NoLockAsync() } finally { - _logger.LogInformation("Exiting server"); + Logger.LogInformation("Exiting server"); _serverExitedSource.TrySetResult(null); } } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs index 41c4bf4001219..b849768f60027 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLspLogger.cs @@ -9,11 +9,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractLspLogger : ILspLogger -#else internal abstract class AbstractLspLogger : ILspLogger -#endif { public abstract void LogDebug(string message, params object[] @params); public abstract void LogStartContext(string message, params object[] @params); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs index adcc85e1c484b..84849c484dc7a 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestContextFactory.cs @@ -21,11 +21,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// /// The type of the RequestContext to be used by the handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractRequestContextFactory -#else internal abstract class AbstractRequestContextFactory -#endif { /// /// Create a object from the given . diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs index 7e5ea5c8aeb5a..27762af48782d 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractRequestScope.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractRequestScope(string name) : IDisposable -#else internal abstract class AbstractRequestScope(string name) : IDisposable -#endif { public string Name { get; } = name; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs index 7df8edce2f545..c5ccdf85ab942 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractTelemetryService.cs @@ -7,11 +7,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public abstract class AbstractTelemetryService -#else internal abstract class AbstractTelemetryService -#endif { public abstract AbstractRequestScope CreateRequestScope(string lspMethodName); } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs index ac0e0bd0ad03b..93d0b2f64e2b5 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/HandlerProvider.cs @@ -15,7 +15,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// -internal class HandlerProvider : AbstractHandlerProvider, IHandlerProvider +internal class HandlerProvider : AbstractHandlerProvider { private readonly ILspServices _lspServices; private ImmutableDictionary>? _requestHandlers; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs index aeb2a3d698f06..564898a683de1 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializeHandler.cs @@ -11,11 +11,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.Handlers; [LanguageServerEndpoint("initialize", LanguageServerConstants.DefaultLanguageName)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class InitializeHandler -#else internal class InitializeHandler -#endif : IRequestHandler { private readonly IInitializeManager _capabilitiesManager; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs index 46f87a4c52df4..d4a14851e4a70 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Handlers/InitializedHandler.cs @@ -12,11 +12,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework.Handlers; [LanguageServerEndpoint("initialized", LanguageServerConstants.DefaultLanguageName)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class InitializedHandler : INotificationHandler -#else internal class InitializedHandler : INotificationHandler -#endif { private bool HasBeenInitialized = false; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs deleted file mode 100644 index 837cd4b051de5..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IHandlerProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable -#nullable enable - -using System; -using System.Collections.Immutable; - -namespace Microsoft.CommonLanguageServerProtocol.Framework; - -/// -/// Manages handler discovery and distribution. -/// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IHandlerProvider -#else -internal interface IHandlerProvider -#endif -{ - ImmutableArray GetRegisteredMethods(); - - IMethodHandler GetMethodHandler(string method, Type? requestType, Type? responseType); -} diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs index 13e9fc9293a3e..2a73d500c0919 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IInitializeManager.cs @@ -6,12 +6,7 @@ #nullable enable namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IInitializeManager -#else internal interface IInitializeManager -#endif { /// /// Gets a response to be used for "initialize", completing the negoticaitons between client and server. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs index 73b17b89df6ad..b8f704c68d15f 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILifeCycleManager.cs @@ -13,11 +13,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An optional component to run additional logic when LSP shutdown and exit are called, /// for example logging messages, cleaning up custom resources, etc. /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ILifeCycleManager -#else internal interface ILifeCycleManager -#endif { /// /// Called when the server recieves the LSP exit notification. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs index 3bfe075fc3e0e..e7ac139a4ddc2 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspLogger.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ILspLogger -#else internal interface ILspLogger -#endif { void LogStartContext(string message, params object[] @params); void LogEndContext(string message, params object[] @params); diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs index 14ed351fb7e7c..7b094ed23ea64 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ILspServices.cs @@ -10,12 +10,7 @@ using System.Collections.Immutable; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ILspServices : IDisposable -#else internal interface ILspServices : IDisposable -#endif { T GetRequiredService() where T : notnull; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs index d8299e27250a1..6c637662c618d 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IMethodHandler.cs @@ -10,11 +10,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// /// Top level type for LSP request handler. /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IMethodHandler -#else internal interface IMethodHandler -#endif { /// /// Whether or not the solution state on the server is modified as a part of handling this request. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs index 22c8d8f2c38a1..513a78dcc9c70 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/INotificationHandler.cs @@ -14,11 +14,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An interface for handlers of methods which do not return a response and receive no parameters. /// /// The type of the RequestContext to be used by this handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface INotificationHandler : IMethodHandler -#else internal interface INotificationHandler : IMethodHandler -#endif { Task HandleNotificationAsync(TRequestContext requestContext, CancellationToken cancellationToken); } @@ -28,11 +24,7 @@ internal interface INotificationHandler : IMethodHandler /// /// The type of the Request parameter to be received. /// The type of the RequestContext to be used by this handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface INotificationHandler : IMethodHandler -#else internal interface INotificationHandler : IMethodHandler -#endif { Task HandleNotificationAsync(TRequest request, TRequestContext requestContext, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs index 832bbf4fdeae2..23f1ea4cd188e 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IQueueItem.cs @@ -15,11 +15,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An item to be queued for execution. /// /// The type of the request context to be passed along to the handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IQueueItem -#else internal interface IQueueItem -#endif { /// /// Executes the work specified by this queue item. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs deleted file mode 100644 index ee07e97cfdc0a..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestContextFactory.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Threading; -using System.Threading.Tasks; - -// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable -#nullable enable - -namespace Microsoft.CommonLanguageServerProtocol.Framework; - -/// -/// -/// A factory for creating objects from 's. -/// -/// -/// RequestContext's are useful for passing document context, since by default -/// is run on the queue thread (and thus no mutating requests may be executing simultaneously, preventing possible race conditions). -/// It also allows somewhere to pass things like the or which are useful on a wide variety of requests. -/// -/// -/// The type of the RequestContext to be used by the handler. -[Obsolete($"Use {nameof(AbstractRequestContextFactory)} instead.", error: false)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestContextFactory -#else -internal interface IRequestContextFactory -#endif -{ - /// - /// Create a object from the given . - /// Note - throwing in the implementation of this method will cause the server to shutdown. - /// - /// The from which to create a request. - /// The request parameters. - /// - /// The for this request. - /// This method is called on the queue thread to allow context to be retrieved serially, without the possibility of race conditions from Mutating requests. - Task CreateRequestContextAsync(IQueueItem queueItem, TRequestParam requestParam, CancellationToken cancellationToken); -} diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs index 1268202d4328f..463e597c70cf3 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestExecutionQueue.cs @@ -15,11 +15,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// Queues requests to be executed in the proper order. /// /// The type of the RequestContext to be used by the handler. -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestExecutionQueue : IAsyncDisposable -#else internal interface IRequestExecutionQueue : IAsyncDisposable -#endif { /// /// Queue a request. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs index 2bc711aabe3a2..7d58b68427183 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/IRequestHandler.cs @@ -9,12 +9,7 @@ using System.Threading.Tasks; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestHandler : IMethodHandler -#else internal interface IRequestHandler : IMethodHandler -#endif { /// /// Handles an LSP request in the context of the supplied document and/or solution. @@ -26,11 +21,7 @@ internal interface IRequestHandler : IMeth Task HandleRequestAsync(TRequest request, TRequestContext context, CancellationToken cancellationToken); } -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface IRequestHandler : IMethodHandler -#else internal interface IRequestHandler : IMethodHandler -#endif { /// /// Handles an LSP request in the context of the supplied document and/or solution. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs index eaf30967bc0b9..8c746ab34f188 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/ITextDocumentIdentifierHandler.cs @@ -7,11 +7,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public interface ITextDocumentIdentifierHandler : ITextDocumentIdentifierHandler -#else internal interface ITextDocumentIdentifierHandler : ITextDocumentIdentifierHandler -#endif { /// /// Gets the identifier of the document from the request, if the request provides one. @@ -19,10 +15,6 @@ internal interface ITextDocumentIdentifierHandler /// Default language name for use with and . diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs index 1f8c71ce2d7ab..84b972359ce00 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/LanguageServerEndpointAttribute.cs @@ -14,11 +14,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// An attribute which identifies the method which an implements. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = false)] -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class LanguageServerEndpointAttribute : Attribute -#else internal class LanguageServerEndpointAttribute : Attribute -#endif { /// /// Contains the method that this implements. diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems index 3e94a73112030..4f299de12be5e 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/Microsoft.CommonLanguageServerProtocol.Framework.Shared.projitems @@ -18,7 +18,6 @@ - @@ -26,7 +25,6 @@ - @@ -36,7 +34,6 @@ - \ No newline at end of file diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs index 5cf8e93cc2383..31dff9c3b2b81 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/QueueItem.cs @@ -86,30 +86,16 @@ public static (IQueueItem, Task) Create( return (queueItem, queueItem._completionSource.Task); } -#pragma warning disable CS0618 // Type or member is obsolete public async Task CreateRequestContextAsync(IMethodHandler handler, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); _requestTelemetryScope?.RecordExecutionStart(); - var requestContextFactory = (AbstractRequestContextFactory?)LspServices.TryGetService(typeof(AbstractRequestContextFactory)); - if (requestContextFactory is not null) - { - var context = await requestContextFactory.CreateRequestContextAsync(this, handler, _request, cancellationToken).ConfigureAwait(false); - return context; - } - - var obsoleteContextFactory = (IRequestContextFactory?)LspServices.TryGetService(typeof(IRequestContextFactory)); - if (obsoleteContextFactory is not null) - { - var context = await obsoleteContextFactory.CreateRequestContextAsync(this, _request, cancellationToken).ConfigureAwait(false); - return context; - } - - throw new InvalidOperationException($"No {nameof(AbstractRequestContextFactory)} or {nameof(IRequestContextFactory)} was registered with {nameof(ILspServices)}."); + var requestContextFactory = LspServices.GetRequiredService>(); + var context = await requestContextFactory.CreateRequestContextAsync(this, handler, _request, cancellationToken).ConfigureAwait(false); + return context; } -#pragma warning restore CS0618 // Type or member is obsolete /// /// Processes the queued request. Exceptions will be sent to the task completion source diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs index b24ca7e75352d..cd200a1ca0ec7 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs @@ -50,11 +50,7 @@ namespace Microsoft.CommonLanguageServerProtocol.Framework; /// more messages, and a new queue will need to be created. /// /// -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class RequestExecutionQueue : IRequestExecutionQueue -#else internal class RequestExecutionQueue : IRequestExecutionQueue -#endif { protected readonly ILspLogger _logger; protected readonly AbstractHandlerProvider _handlerProvider; @@ -75,19 +71,6 @@ internal class RequestExecutionQueue : IRequestExecutionQueue _cancelSource.Token; - [Obsolete($"Use constructor with {nameof(AbstractHandlerProvider)} instead.", error: false)] - public RequestExecutionQueue(AbstractLanguageServer languageServer, ILspLogger logger, IHandlerProvider handlerProvider) - { - _languageServer = languageServer; - _logger = logger; - if (handlerProvider is AbstractHandlerProvider abstractHandlerProvider) - { - _handlerProvider = abstractHandlerProvider; - } - - _handlerProvider = new WrappedHandlerProvider(handlerProvider); - } - public RequestExecutionQueue(AbstractLanguageServer languageServer, ILspLogger logger, AbstractHandlerProvider handlerProvider) { _languageServer = languageServer; diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs index e9f7a2be78ed5..c0b0bd4254d43 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestHandlerMetadata.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public record RequestHandlerMetadata(string MethodName, Type? RequestType, Type? ResponseType, string Language) -#else internal record RequestHandlerMetadata(string MethodName, Type? RequestType, Type? ResponseType, string Language) -#endif { internal string HandlerDescription { get; } = $"{MethodName} ({Language})"; } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs index e689d852425f1..9ad2023834c93 100644 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs +++ b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestShutdownEventArgs.cs @@ -8,12 +8,7 @@ using System; namespace Microsoft.CommonLanguageServerProtocol.Framework; - -#if BINARY_COMPAT // TODO - Remove with https://github.com/dotnet/roslyn/issues/72251 -public class RequestShutdownEventArgs : EventArgs -#else internal class RequestShutdownEventArgs : EventArgs -#endif { public string Message { get; } diff --git a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs b/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs deleted file mode 100644 index 120b54dfb0c21..0000000000000 --- a/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/WrappedHandlerProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// This is consumed as 'generated' code in a source package and therefore requires an explicit nullable enable -#nullable enable - -using System; -using System.Collections.Immutable; - -namespace Microsoft.CommonLanguageServerProtocol.Framework; - -/// -/// Wraps an . -/// -internal sealed class WrappedHandlerProvider : AbstractHandlerProvider -{ - private readonly IHandlerProvider _handlerProvider; - - public WrappedHandlerProvider(IHandlerProvider handlerProvider) - { - _handlerProvider = handlerProvider; - } - - public override IMethodHandler GetMethodHandler(string method, Type? requestType, Type? responseType, string language) - => _handlerProvider.GetMethodHandler(method, requestType, responseType); - - public override ImmutableArray GetRegisteredMethods() - => _handlerProvider.GetRegisteredMethods(); -} diff --git a/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs b/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs index 3dc9a54510650..e59664f09dd6f 100644 --- a/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs +++ b/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs @@ -54,7 +54,7 @@ protected override ILspServices ConstructLspServices() protected override IRequestExecutionQueue ConstructRequestExecutionQueue() { var provider = GetLspServices().GetRequiredService>(); - return provider.CreateRequestExecutionQueue(this, _logger, HandlerProvider); + return provider.CreateRequestExecutionQueue(this, Logger, HandlerProvider); } private ImmutableDictionary>> GetBaseServices( @@ -115,7 +115,7 @@ protected override string GetLanguageForRequest(string methodName, JToken? param { if (parameters == null) { - _logger.LogInformation("No request parameters given, using default language handler"); + Logger.LogInformation("No request parameters given, using default language handler"); return LanguageServerConstants.DefaultLanguageName; } @@ -140,7 +140,7 @@ protected override string GetLanguageForRequest(string methodName, JToken? param var uri = uriToken.ToObject(_jsonSerializer); Contract.ThrowIfNull(uri, "Failed to deserialize uri property"); var language = lspWorkspaceManager.GetLanguageForUri(uri); - _logger.LogInformation($"Using {language} from request text document"); + Logger.LogInformation($"Using {language} from request text document"); return language; } @@ -154,12 +154,12 @@ protected override string GetLanguageForRequest(string methodName, JToken? param var data = dataToken.ToObject(_jsonSerializer); Contract.ThrowIfNull(data, "Failed to document resolve data object"); var language = lspWorkspaceManager.GetLanguageForUri(data.TextDocument.Uri); - _logger.LogInformation($"Using {language} from data text document"); + Logger.LogInformation($"Using {language} from data text document"); return language; } // This request is not for a textDocument and is not a resolve request. - _logger.LogInformation("Request did not contain a textDocument, using default language handler"); + Logger.LogInformation("Request did not contain a textDocument, using default language handler"); return LanguageServerConstants.DefaultLanguageName; static bool ShouldUseDefaultLanguage(string methodName) diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index 5d2ff4a4c435a..d739438f4952f 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -135,12 +135,6 @@ true BindingRedirect - - Microsoft.CommonLanguageServerProtocolFramework - BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup - true - BindingRedirect - LiveShareLanguageServices BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup From e83a143d4d8f9f5e022b930bba537f4beaa2c094 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 19:39:35 -0700 Subject: [PATCH 102/292] Store as checksum --- .../Portable/Serialization/SerializableSourceText.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 0a7bcf15f227c..ad9ded5d6bd10 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -45,12 +45,6 @@ internal sealed class SerializableSourceText /// private readonly SourceText? _text; - /// - /// The hash that would be produced by calling on . Can be passed in when already known to avoid unnecessary computation costs. - /// - private readonly ImmutableArray _contentHash; - /// /// Weak reference to a SourceText computed from . Useful so that if multiple requests /// come in for the source text, the same one can be returned as long as something is holding it alive. @@ -60,7 +54,7 @@ internal sealed class SerializableSourceText /// /// Checksum of the contents (see ) of the text. /// - public Checksum ContentChecksum => Checksum.Create(_contentHash); + public readonly Checksum ContentChecksum; public SerializableSourceText(TemporaryTextStorage storage, ImmutableArray contentHash) : this(storage, text: null, contentHash) @@ -78,7 +72,7 @@ private SerializableSourceText(TemporaryTextStorage? storage, SourceText? text, _storage = storage; _text = text; - _contentHash = contentHash; + ContentChecksum = Checksum.Create(contentHash); #if DEBUG var computedContentHash = TryGetText()?.GetContentHash() ?? _storage!.ContentHash; From 2044967360e677f1ba2285df510342275b81c0a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 23:02:06 -0700 Subject: [PATCH 103/292] skip test --- .../IntegrationTest/New.IntegrationTests/InfrastructureTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs index c11174300d08a..f520004406aed 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs @@ -22,7 +22,7 @@ public InfrastructureTests() protected override string LanguageName => LanguageNames.CSharp; - [IdeFact] + [IdeFact(Skip = "https://github.com/dotnet/roslyn/issues/73099")] public async Task CanCloseSaveDialog() { await SetUpEditorAsync( From c58aa81fae15c791b4629bf9d95b18774ed364d9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 18 Apr 2024 23:02:06 -0700 Subject: [PATCH 104/292] skip test --- .../IntegrationTest/New.IntegrationTests/InfrastructureTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs index c11174300d08a..f520004406aed 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InfrastructureTests.cs @@ -22,7 +22,7 @@ public InfrastructureTests() protected override string LanguageName => LanguageNames.CSharp; - [IdeFact] + [IdeFact(Skip = "https://github.com/dotnet/roslyn/issues/73099")] public async Task CanCloseSaveDialog() { await SetUpEditorAsync( From 27a91c523036658cfbd8f9408181bd70910b2a71 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 00:42:15 -0700 Subject: [PATCH 105/292] Docs --- .../Core/Portable/Workspace/Solution/TextLoader.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs index 9b4ab7c3e2d86..cf625034f68d8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs @@ -33,13 +33,15 @@ public abstract class TextLoader /// /// if the document that holds onto this loader should do so with a strong reference, versus - /// a reference that will take the contents of this loader and store them in a recoverable form. This should be - /// used when the underlying data is already stored in a recoverable form somewhere else and it would be wasteful to - /// store another copy. For example, a document that is backed by memory-mapped contents in another process does not - /// need to dump it's content to another memory-mapped file in the process it lives in. It can always recover the - /// text from the original process. + /// a reference that will take the contents of this loader and store them in a recoverable form (e.g. a memory + /// mapped file within the same process). This should be used when the underlying data is already stored + /// in a recoverable form somewhere else and it would be wasteful to store another copy. For example, a document + /// that is backed by memory-mapped contents in another process does not need to dump it's content to + /// another memory-mapped file in the process it lives in. It can always recover the text from the original + /// process. /// - internal virtual bool AlwaysHoldStrongly => false; + internal virtual bool AlwaysHoldStrongly + => false; /// /// True if reloads from its original binary representation (e.g. file on disk). From fcbdd25748028f478113961031a6741127c139ec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 00:46:20 -0700 Subject: [PATCH 106/292] Docs --- .../Core/Portable/Workspace/Solution/TextDocumentState.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 1ec64287e0972..bc4f27fc513c6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -175,6 +175,11 @@ private static ITextAndVersionSource CreateTextFromLoader(TextLoader loader, Pre var service = solutionServices.GetRequiredService(); var options = service.Options; + // See if the client, the loader itself, or the options want us to hold onto this loader strongly. If so, we + // wrap it directly in a LoadableTextAndVersionSource that will keep it alive, deferring to it to get the final + // source text. If none of the above hold, we'll create a RecoverableTextAndVersion. This will use the loaded + // the first time to get the text contents, but will then dump those contents to a memory-mapped-file afterwards + // so that it can be kept out of main memory, while still allowing us to preserve snapshot semantics. return mode == PreservationMode.PreserveIdentity || loader.AlwaysHoldStrongly || options.DisableRecoverableText ? new LoadableTextAndVersionSource(loader, cacheResult: true) : new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), solutionServices); From 4f4f9bd032df38c7e0ef5c7300758c27c194ef13 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 00:49:05 -0700 Subject: [PATCH 107/292] Docs --- .../Host/RemoteWorkspace.SolutionCreator.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 9bf1cccabf9c4..9523c67fac044 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -619,12 +619,11 @@ private async Task UpdateDocumentAsync( assetPath: document.Id, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); var loader = serializableSourceText.ToTextLoader(document.FilePath); - // We strongly want to use identity-preservation with these loaders. By doing that we'll always use - // this loader as the 'truth' of how to get teh source-text for a particular document. When the - // text is first needed it will be pulled from the memory mapped files on the host side. Then, if - // that text is ever dropped, we'll still be pointing at this same loader. That will ensure that we - // still read the data from the host side, without doing something silly (like dumping it to our own - // memory mapped data on the OOP side). + // The mode here doesn't actually matter. The text loaded created by ToTextLoader will already tell + // the solution to use it as the source of truth for the document and to not then dump the contents + // of it to a memory-mapped-file within this process (no point when it's already that way in the + // shared memory-mapped-file we are pointing to in the host process). But 'PreserveIdentity' + // matches that as well so we use that mode here for clarity. var mode = PreservationMode.PreserveIdentity; document = document.Kind switch From a45748cc9c5faf264a1f23e72138a0e5ceddd87d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 01:02:49 -0700 Subject: [PATCH 108/292] revert --- .../ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 9523c67fac044..5a44050959030 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -618,13 +618,7 @@ private async Task UpdateDocumentAsync( var serializableSourceText = await _assetProvider.GetAssetAsync( assetPath: document.Id, newDocumentChecksums.textChecksum, cancellationToken).ConfigureAwait(false); var loader = serializableSourceText.ToTextLoader(document.FilePath); - - // The mode here doesn't actually matter. The text loaded created by ToTextLoader will already tell - // the solution to use it as the source of truth for the document and to not then dump the contents - // of it to a memory-mapped-file within this process (no point when it's already that way in the - // shared memory-mapped-file we are pointing to in the host process). But 'PreserveIdentity' - // matches that as well so we use that mode here for clarity. - var mode = PreservationMode.PreserveIdentity; + var mode = PreservationMode.PreserveValue; document = document.Kind switch { From 8ac8c86ce8bae98c1cfc8da6b873f28027147614 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 01:07:12 -0700 Subject: [PATCH 109/292] Fix --- .../Workspace/Solution/TextDocumentState.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index bc4f27fc513c6..53c6008610ccc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -3,9 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; @@ -175,14 +173,22 @@ private static ITextAndVersionSource CreateTextFromLoader(TextLoader loader, Pre var service = solutionServices.GetRequiredService(); var options = service.Options; - // See if the client, the loader itself, or the options want us to hold onto this loader strongly. If so, we - // wrap it directly in a LoadableTextAndVersionSource that will keep it alive, deferring to it to get the final - // source text. If none of the above hold, we'll create a RecoverableTextAndVersion. This will use the loaded - // the first time to get the text contents, but will then dump those contents to a memory-mapped-file afterwards - // so that it can be kept out of main memory, while still allowing us to preserve snapshot semantics. - return mode == PreservationMode.PreserveIdentity || loader.AlwaysHoldStrongly || options.DisableRecoverableText - ? new LoadableTextAndVersionSource(loader, cacheResult: true) - : new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), solutionServices); + // If the caller is explicitly stating that identity must be preserved, then we created a source that will load + // from the loader the first time, but then cache that result so that hte same result is *always* returned. + if (mode == PreservationMode.PreserveIdentity || options.DisableRecoverableText) + return new LoadableTextAndVersionSource(loader, cacheResult: true); + + // If the loader asks us to always hold onto it strongly, then we do not want to create a recoverable text + // source here. Instead, we'll go back to the loader each time to get the text. This is useful for when the + // loader knows it can always reconstitute the snapshot exactly as it was before. For example, if the loader + // points at the contents of a memory mapped file in another process. + if (loader.AlwaysHoldStrongly) + return new LoadableTextAndVersionSource(loader, cacheResult: false); + + // Otherwise, we just want to hold onto this loader by value. So we create a loader that will load the + // contents, but not hold onto them strongly, and we wrap it in a recoverable-text that will then take those + // contents and dump it into a memory-mapped-file in this process so that snapshot semantics can be preserved. + return new RecoverableTextAndVersion(new LoadableTextAndVersionSource(loader, cacheResult: false), solutionServices); } protected virtual TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental) From 7c55f24208ad97d6ee949472b91386a851bfecfc Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Fri, 19 Apr 2024 07:01:12 -0700 Subject: [PATCH 110/292] Change IReferenceFinder.DetermineDocumentsToSearchAsync to not return an ImmutableArray (#73093) Change IReferenceFinder.DetermineDocumentsToSearchAsync to not return an ImmutableArray Rather, this method now encourages items that wish to be part of the result to invoke a callback with that result. This prevents quite a few intermediary array allocations during find references invocations. --- .../DelegateInvokeMethodReferenceFinder.cs | 10 ++- .../FindReferencesSearchEngine.cs | 16 +++-- .../AbstractMemberScopedReferenceFinder.cs | 14 ++-- .../Finders/AbstractReferenceFinder.cs | 71 +++++++++++-------- ...tructorInitializerSymbolReferenceFinder.cs | 9 +-- .../ConstructorSymbolReferenceFinder.cs | 48 ++++++------- .../DestructorSymbolReferenceFinder.cs | 8 ++- .../Finders/EventSymbolReferenceFinder.cs | 10 +-- ...ExplicitConversionSymbolReferenceFinder.cs | 17 ++--- .../ExplicitInterfaceMethodReferenceFinder.cs | 8 ++- .../Finders/FieldSymbolReferenceFinder.cs | 11 +-- .../Finders/IReferenceFinder.cs | 4 +- ...ethodTypeParameterSymbolReferenceFinder.cs | 7 +- .../Finders/NamedTypeSymbolReferenceFinder.cs | 39 +++++----- .../Finders/NamespaceSymbolReferenceFinder.cs | 23 +++--- .../Finders/OperatorSymbolReferenceFinder.cs | 19 ++--- .../Finders/OrdinaryMethodReferenceFinder.cs | 45 ++++++------ .../Finders/ParameterSymbolReferenceFinder.cs | 6 +- .../PropertyAccessorSymbolReferenceFinder.cs | 16 ++--- .../Finders/PropertySymbolReferenceFinder.cs | 36 +++++----- .../TypeParameterSymbolReferenceFinder.cs | 7 +- 21 files changed, 232 insertions(+), 192 deletions(-) diff --git a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs index 023fb637ee227..8b1bd852308cb 100644 --- a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs +++ b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -59,15 +60,20 @@ protected override async ValueTask> DetermineCascadedSym return result.ToImmutableAndClear(); } - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return Task.FromResult(project.Documents.ToImmutableArray()); + foreach (var document in project.Documents) + processResult(document, processResultData); + + return Task.CompletedTask; } protected override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 8d788e83130bb..25f61c8deda53 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -190,6 +190,9 @@ private async Task ProcessProjectAsync(Project project, ImmutableArray using var _2 = PooledDictionary.GetInstance(out var documentToSymbols); try { + // scratch hashset to place results in. Populated/inspected/cleared in inner loop. + using var _3 = PooledHashSet.GetInstance(out var foundDocuments); + await AddGlobalAliasesAsync(project, allSymbols, symbolToGlobalAliases, cancellationToken).ConfigureAwait(false); foreach (var symbol in allSymbols) @@ -198,18 +201,23 @@ private async Task ProcessProjectAsync(Project project, ImmutableArray foreach (var finder in _finders) { - var documents = await finder.DetermineDocumentsToSearchAsync( - symbol, globalAliases, project, _documents, _options, cancellationToken).ConfigureAwait(false); + await finder.DetermineDocumentsToSearchAsync( + symbol, globalAliases, project, _documents, + static (doc, documents) => documents.Add(doc), + foundDocuments, + _options, cancellationToken).ConfigureAwait(false); - foreach (var document in documents) + foreach (var document in foundDocuments) { var docSymbols = GetSymbolSet(documentToSymbols, document); docSymbols.Add(symbol); } + + foundDocuments.Clear(); } } - using var _3 = ArrayBuilder.GetInstance(out var tasks); + using var _4 = ArrayBuilder.GetInstance(out var tasks); foreach (var (document, docSymbols) in documentToSymbols) { tasks.Add(CreateWorkAsync(() => ProcessDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 224ab915fc901..5e70cfac3b188 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -23,26 +24,29 @@ protected abstract bool TokensMatch( protected sealed override bool CanFind(TSymbol symbol) => true; - protected sealed override Task> DetermineDocumentsToSearchAsync( + protected sealed override Task DetermineDocumentsToSearchAsync( TSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var location = symbol.Locations.FirstOrDefault(); if (location == null || !location.IsInSource) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; var document = project.GetDocument(location.SourceTree); if (document == null) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; if (documents != null && !documents.Contains(document)) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; - return Task.FromResult(ImmutableArray.Create(document)); + processResult(document, processResultData); + return Task.CompletedTask; } protected sealed override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index 1ac57d9d93596..c2a586f94d1e3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -24,14 +24,17 @@ internal abstract partial class AbstractReferenceFinder : IReferenceFinder public const string ContainingTypeInfoPropertyName = "ContainingTypeInfo"; public const string ContainingMemberInfoPropertyName = "ContainingMemberInfo"; + protected static readonly Action> StandardHashSetAddCallback = + static (doc, set) => set.Add(doc); + public abstract Task> DetermineGlobalAliasesAsync( ISymbol symbol, Project project, CancellationToken cancellationToken); public abstract ValueTask> DetermineCascadedSymbolsAsync( ISymbol symbol, Solution solution, FindReferencesSearchOptions options, CancellationToken cancellationToken); - public abstract Task> DetermineDocumentsToSearchAsync( - ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken); + public abstract Task DetermineDocumentsToSearchAsync( + ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); public abstract ValueTask> FindReferencesInDocumentAsync( ISymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken); @@ -81,11 +84,13 @@ protected static bool TryGetNameWithoutAttributeSuffix( return name.TryGetWithoutAttributeSuffix(syntaxFacts.IsCaseSensitive, out result); } - protected static async Task> FindDocumentsAsync( + protected static async Task FindDocumentsAsync( Project project, IImmutableSet? scope, Func> predicateAsync, T value, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { // special case for highlight references @@ -93,31 +98,30 @@ protected static async Task> FindDocumentsAsync( { var document = scope.First(); if (document.Project == project) - return scope.ToImmutableArray(); + processResult(document, processResultData); - return []; + return; } - using var _ = ArrayBuilder.GetInstance(out var documents); foreach (var document in await project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)) { if (scope != null && !scope.Contains(document)) continue; if (await predicateAsync(document, value, cancellationToken).ConfigureAwait(false)) - documents.Add(document); + processResult(document, processResultData); } - - return documents.ToImmutableAndClear(); } /// /// Finds all the documents in the provided project that contain the requested string /// values /// - protected static Task> FindDocumentsAsync( + protected static Task FindDocumentsAsync( Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, CancellationToken cancellationToken, params string[] values) { @@ -130,30 +134,32 @@ protected static Task> FindDocumentsAsync( } return true; - }, values, cancellationToken); + }, values, processResult, processResultData, cancellationToken); } /// /// Finds all the documents in the provided project that contain a global attribute in them. /// - protected static Task> FindDocumentsWithGlobalSuppressMessageAttributeAsync( - Project project, IImmutableSet? documents, CancellationToken cancellationToken) + protected static Task FindDocumentsWithGlobalSuppressMessageAttributeAsync( + Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( - project, documents, static index => index.ContainsGlobalSuppressMessageAttribute, cancellationToken); + project, documents, static index => index.ContainsGlobalSuppressMessageAttribute, processResult, processResultData, cancellationToken); } - protected static Task> FindDocumentsAsync( + protected static Task FindDocumentsAsync( Project project, IImmutableSet? documents, PredefinedType predefinedType, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (predefinedType == PredefinedType.None) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; return FindDocumentsWithPredicateAsync( - project, documents, static (index, predefinedType) => index.ContainsPredefinedType(predefinedType), predefinedType, cancellationToken); + project, documents, static (index, predefinedType) => index.ContainsPredefinedType(predefinedType), predefinedType, processResult, processResultData, cancellationToken); } protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string name, SyntaxToken token) @@ -327,35 +333,41 @@ private static async Task> FindReferencesThroughL return allAliasReferences.ToImmutableAndClear(); } - protected static Task> FindDocumentsWithPredicateAsync( + protected static Task FindDocumentsWithPredicateAsync( Project project, IImmutableSet? documents, Func predicate, T value, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return FindDocumentsAsync(project, documents, static async (d, t, c) => { var info = await SyntaxTreeIndex.GetRequiredIndexAsync(d, c).ConfigureAwait(false); return t.predicate(info, t.value); - }, (predicate, value), cancellationToken); + }, (predicate, value), processResult, processResultData, cancellationToken); } - protected static Task> FindDocumentsWithPredicateAsync( + protected static Task FindDocumentsWithPredicateAsync( Project project, IImmutableSet? documents, Func predicate, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( project, documents, static (info, predicate) => predicate(info), predicate, + processResult, + processResultData, cancellationToken); } - protected static Task> FindDocumentsWithForEachStatementsAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsForEachStatement, cancellationToken); + protected static Task FindDocumentsWithForEachStatementsAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsForEachStatement, processResult, processResultData, cancellationToken); /// /// If the `node` implicitly matches the `symbol`, then it will be added to `locations`. @@ -824,8 +836,9 @@ internal abstract partial class AbstractReferenceFinder : AbstractRefer { protected abstract bool CanFind(TSymbol symbol); - protected abstract Task> DetermineDocumentsToSearchAsync( + protected abstract Task DetermineDocumentsToSearchAsync( TSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); protected abstract ValueTask> FindReferencesInDocumentAsync( @@ -845,13 +858,15 @@ public sealed override Task> DetermineGlobalAliasesAsync( : SpecializedTasks.EmptyImmutableArray(); } - public sealed override Task> DetermineDocumentsToSearchAsync( + public sealed override Task DetermineDocumentsToSearchAsync( ISymbol symbol, HashSet? globalAliases, Project project, - IImmutableSet? documents, FindReferencesSearchOptions options, CancellationToken cancellationToken) + IImmutableSet? documents, Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return symbol is TSymbol typedSymbol && CanFind(typedSymbol) - ? DetermineDocumentsToSearchAsync(typedSymbol, globalAliases, project, documents, options, cancellationToken) - : SpecializedTasks.EmptyImmutableArray(); + if (symbol is TSymbol typedSymbol && CanFind(typedSymbol)) + return DetermineDocumentsToSearchAsync(typedSymbol, globalAliases, project, documents, processResult, processResultData, options, cancellationToken); + + return Task.CompletedTask; } public sealed override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index 88dc9fad96af2..c79b50715238d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -19,11 +18,13 @@ internal sealed class ConstructorInitializerSymbolReferenceFinder : AbstractRefe protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.Constructor; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -47,7 +48,7 @@ protected override Task> DetermineDocumentsToSearchAsyn } return false; - }, symbol.ContainingType.Name, cancellationToken); + }, symbol.ContainingType.Name, processResult, processResultData, cancellationToken); } protected sealed override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index f4e3e0828a5d5..8fea80d2ffb77 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -32,62 +31,59 @@ protected override Task> DetermineGlobalAliasesAsync(IMet return GetAllMatchingGlobalAliasNamesAsync(project, containingType.Name, containingType.Arity, cancellationToken); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var containingType = symbol.ContainingType; var typeName = symbol.ContainingType.Name; - using var _ = ArrayBuilder.GetInstance(out var result); - await AddDocumentsAsync( - project, documents, typeName, result, cancellationToken).ConfigureAwait(false); + project, documents, typeName, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (globalAliases != null) { foreach (var globalAlias in globalAliases) { await AddDocumentsAsync( - project, documents, globalAlias, result, cancellationToken).ConfigureAwait(false); + project, documents, globalAlias, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } - result.AddRange(await FindDocumentsAsync( - project, documents, containingType.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false)); - - result.AddRange(await FindDocumentsWithGlobalSuppressMessageAttributeAsync( - project, documents, cancellationToken).ConfigureAwait(false)); + await FindDocumentsAsync( + project, documents, containingType.SpecialType.ToPredefinedType(), processResult, processResultData, cancellationToken).ConfigureAwait(false); - result.AddRange(symbol.MethodKind == MethodKind.Constructor - ? await FindDocumentsWithImplicitObjectCreationExpressionAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return result.ToImmutableAndClear(); + if (symbol.MethodKind == MethodKind.Constructor) + { + await FindDocumentsWithImplicitObjectCreationExpressionAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); + } } - private static Task> FindDocumentsWithImplicitObjectCreationExpressionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsImplicitObjectCreation, cancellationToken); + private static Task FindDocumentsWithImplicitObjectCreationExpressionAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsImplicitObjectCreation, processResult, processResultData, cancellationToken); - private static async Task AddDocumentsAsync( + private static async Task AddDocumentsAsync( Project project, IImmutableSet? documents, string typeName, - ArrayBuilder result, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, typeName).ConfigureAwait(false); - - var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(typeName, project.Services.GetRequiredService(), out var simpleName) - ? await FindDocumentsAsync(project, documents, cancellationToken, simpleName).ConfigureAwait(false) - : []; + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, typeName).ConfigureAwait(false); - result.AddRange(documentsWithName); - result.AddRange(documentsWithAttribute); + if (TryGetNameWithoutAttributeSuffix(typeName, project.Services.GetRequiredService(), out var simpleName)) + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, simpleName).ConfigureAwait(false); } private static bool IsPotentialReference(PredefinedType predefinedType, ISyntaxFactsService syntaxFacts, SyntaxToken token) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs index 610db4da1aeac..245f390365b46 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -15,15 +15,17 @@ internal sealed class DestructorSymbolReferenceFinder : AbstractReferenceFinder< protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.Destructor; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; } protected override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 756bffd4366c4..76a169a867822 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -33,17 +34,18 @@ protected sealed override ValueTask> DetermineCascadedSy return new(backingFields.Concat(associatedNamedTypes)); } - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IEventSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithName.Concat(documentsWithGlobalAttributes); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } protected sealed override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs index 7059fefdf72ab..b4e00a0482da6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -23,11 +24,13 @@ protected override bool CanFind(IMethodSymbol symbol) private static INamedTypeSymbol? GetUnderlyingNamedType(ITypeSymbol symbol) => UnderlyingNamedTypeVisitor.Instance.Visit(symbol); - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -43,20 +46,18 @@ protected sealed override async Task> DetermineDocument var underlyingNamedType = GetUnderlyingNamedType(symbol.ReturnType); Contract.ThrowIfNull(underlyingNamedType); - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); - var documentsWithType = await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var result); + using var _ = PooledHashSet.GetInstance(out var result); + await FindDocumentsAsync(project, documents, StandardHashSetAddCallback, result, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), StandardHashSetAddCallback, result, cancellationToken).ConfigureAwait(false); // Ignore any documents that don't also have an explicit cast in them. - foreach (var document in documentsWithName.Concat(documentsWithType).Distinct()) + foreach (var document in result) { var index = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); if (index.ContainsConversion) - result.Add(document); + processResult(document, processResultData); } - - return result.ToImmutableAndClear(); } protected sealed override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs index 51256d807e4b0..b048e24c1a03f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -15,16 +15,18 @@ internal sealed class ExplicitInterfaceMethodReferenceFinder : AbstractReference protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.ExplicitInterfaceImplementation; - protected sealed override Task> DetermineDocumentsToSearchAsync( + protected sealed override Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // An explicit method can't be referenced anywhere. - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; } protected sealed override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index 43c3c03d7ccdc..d3657581f7cfd 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -26,17 +26,18 @@ protected override ValueTask> DetermineCascadedSymbolsAs : new(ImmutableArray.Empty); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IFieldSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithName.Concat(documentsWithGlobalAttributes); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } protected override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs index 395fcb5402ca3..8e4738b2a4728 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -49,9 +50,10 @@ ValueTask> DetermineCascadedSymbolsAsync( /// /// Implementations of this method must be thread-safe. /// - Task> DetermineDocumentsToSearchAsync( + Task DetermineDocumentsToSearchAsync( ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); /// diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs index 78d29392e7082..55150a8b42bf7 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/MethodTypeParameterSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -36,11 +37,13 @@ protected override ValueTask> DetermineCascadedSymbolsAs return new([]); } - protected sealed override Task> DetermineDocumentsToSearchAsync( + protected sealed override Task DetermineDocumentsToSearchAsync( ITypeParameterSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -55,7 +58,7 @@ protected sealed override Task> DetermineDocumentsToSea // Also, we only look for files that have the name of the owning type. This helps filter // down the set considerably. Contract.ThrowIfNull(symbol.DeclaringMethod); - return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name, + return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name, GetMemberNameWithoutInterfaceName(symbol.DeclaringMethod.Name), symbol.DeclaringMethod.ContainingType.Name); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index 74537c547319e..ff8f1084f9b13 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -10,7 +11,6 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -49,54 +49,49 @@ private static void Add(ArrayBuilder result, ImmutableArray()); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( INamedTypeSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var result); - - await AddDocumentsToSearchAsync(symbol.Name, project, documents, result, cancellationToken).ConfigureAwait(false); + await AddDocumentsToSearchAsync(symbol.Name, project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (globalAliases != null) { foreach (var alias in globalAliases) - await AddDocumentsToSearchAsync(alias, project, documents, result, cancellationToken).ConfigureAwait(false); + await AddDocumentsToSearchAsync(alias, project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - result.AddRange(await FindDocumentsAsync( - project, documents, symbol.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false)); - - result.AddRange(await FindDocumentsWithGlobalSuppressMessageAttributeAsync( - project, documents, cancellationToken).ConfigureAwait(false)); + await FindDocumentsAsync( + project, documents, symbol.SpecialType.ToPredefinedType(), processResult, processResultData, cancellationToken).ConfigureAwait(false); - return result.ToImmutableAndClear(); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } /// /// Looks for documents likely containing in them. That name will either be the actual /// name of the named type we're looking for, or it might be a global alias to it. /// - private static async Task AddDocumentsToSearchAsync( + private static async Task AddDocumentsToSearchAsync( string throughName, Project project, IImmutableSet? documents, - ArrayBuilder result, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var syntaxFacts = project.Services.GetRequiredService(); - var documentsWithName = await FindDocumentsAsync( - project, documents, cancellationToken, throughName).ConfigureAwait(false); - - var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(throughName, syntaxFacts, out var simpleName) - ? await FindDocumentsAsync(project, documents, cancellationToken, simpleName).ConfigureAwait(false) - : []; + await FindDocumentsAsync( + project, documents, processResult, processResultData, cancellationToken, throughName).ConfigureAwait(false); - result.AddRange(documentsWithName); - result.AddRange(documentsWithAttribute); + if (TryGetNameWithoutAttributeSuffix(throughName, syntaxFacts, out var simpleName)) + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, simpleName).ConfigureAwait(false); } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index dc0f2a36f79a3..bb03240ba2ce4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -22,33 +23,31 @@ protected override Task> DetermineGlobalAliasesAsync(INam return GetAllMatchingGlobalAliasNamesAsync(project, symbol.Name, arity: 0, cancellationToken); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( INamespaceSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var result); - - result.AddRange(!symbol.IsGlobalNamespace - ? await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false) - : await FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsGlobalKeyword, cancellationToken).ConfigureAwait(false)); + if (!symbol.IsGlobalNamespace) + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); + else + await FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsGlobalKeyword, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (globalAliases != null) { foreach (var globalAlias in globalAliases) { - result.AddRange(await FindDocumentsAsync( - project, documents, cancellationToken, globalAlias).ConfigureAwait(false)); + await FindDocumentsAsync( + project, documents, processResult, processResultData, cancellationToken, globalAlias).ConfigureAwait(false); } } - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - result.AddRange(documentsWithGlobalAttributes); - - return result.ToImmutableAndClear(); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } protected override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index a4135e8d5f065..bd1d4210e5476 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -2,13 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -17,31 +17,34 @@ internal sealed class OperatorSymbolReferenceFinder : AbstractMethodOrPropertyOr protected override bool CanFind(IMethodSymbol symbol) => symbol.MethodKind is MethodKind.UserDefinedOperator or MethodKind.BuiltinOperator; - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var op = symbol.GetPredefinedOperator(); - var documentsWithOp = await FindDocumentsAsync(project, documents, op, cancellationToken).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithOp.Concat(documentsWithGlobalAttributes); + await FindDocumentsAsync(project, documents, op, processResult, processResultData, cancellationToken).ConfigureAwait(false); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static Task> FindDocumentsAsync( + private static Task FindDocumentsAsync( Project project, IImmutableSet? documents, PredefinedOperator op, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (op == PredefinedOperator.None) - return SpecializedTasks.EmptyImmutableArray(); + return Task.CompletedTask; return FindDocumentsWithPredicateAsync( - project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, cancellationToken); + project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, processResult, processResultData, cancellationToken); } protected sealed override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index 91ab4963aa113..7ace214c57e6e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -42,11 +43,13 @@ private static ImmutableArray GetOtherPartsOfPartial(IMethodSymbol symb return []; } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IMethodSymbol methodSymbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -65,38 +68,32 @@ protected override async Task> DetermineDocumentsToSear // searches for these, then we should find usages of 'lock(goo)' or 'synclock(goo)' // since they implicitly call those methods. - var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, methodSymbol.Name).ConfigureAwait(false); - var forEachDocuments = IsForEachMethod(methodSymbol) - ? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, methodSymbol.Name).ConfigureAwait(false); - var deconstructDocuments = IsDeconstructMethod(methodSymbol) - ? await FindDocumentsWithDeconstructionAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachMethod(methodSymbol)) + await FindDocumentsWithForEachStatementsAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var awaitExpressionDocuments = IsGetAwaiterMethod(methodSymbol) - ? await FindDocumentsWithAwaitExpressionAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (IsDeconstructMethod(methodSymbol)) + await FindDocumentsWithDeconstructionAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync( - project, documents, cancellationToken).ConfigureAwait(false); + if (IsGetAwaiterMethod(methodSymbol)) + await FindDocumentsWithAwaitExpressionAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var documentsWithCollectionInitializers = IsAddMethod(methodSymbol) - ? await FindDocumentsWithCollectionInitializersAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + await FindDocumentsWithGlobalSuppressMessageAttributeAsync( + project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return ordinaryDocuments.Concat( - forEachDocuments, deconstructDocuments, awaitExpressionDocuments, documentsWithGlobalAttributes, documentsWithCollectionInitializers); + if (IsAddMethod(methodSymbol)) + await FindDocumentsWithCollectionInitializersAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static Task> FindDocumentsWithDeconstructionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsDeconstruction, cancellationToken); + private static Task FindDocumentsWithDeconstructionAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsDeconstruction, processResult, processResultData, cancellationToken); - private static Task> FindDocumentsWithAwaitExpressionAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsAwait, cancellationToken); + private static Task FindDocumentsWithAwaitExpressionAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsAwait, processResult, processResultData, cancellationToken); - private static Task> FindDocumentsWithCollectionInitializersAsync(Project project, IImmutableSet? documents, CancellationToken cancellationToken) - => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsCollectionInitializer, cancellationToken); + private static Task FindDocumentsWithCollectionInitializersAsync(Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) + => FindDocumentsWithPredicateAsync(project, documents, static index => index.ContainsCollectionInitializer, processResult, processResultData, cancellationToken); private static bool IsForEachMethod(IMethodSymbol methodSymbol) => methodSymbol.Name is WellKnownMemberNames.GetEnumeratorMethodName or diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index c15af3ce4355e..d7231cad5721a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -20,11 +20,13 @@ internal sealed class ParameterSymbolReferenceFinder : AbstractReferenceFinder true; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( IParameterSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -33,7 +35,7 @@ protected override Task> DetermineDocumentsToSearchAsyn // elsewhere as "paramName:" or "paramName:=". We can narrow the search by // filtering down to matches of that form. For now we just return any document // that references something with this name. - return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name); + return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name); } protected override ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index 383c82ad44096..a15f25ab2979c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -2,13 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -30,34 +29,35 @@ protected override ValueTask> DetermineCascadedSymbolsAs : new(ImmutableArray.Create(symbol.AssociatedSymbol)); } - protected override async Task> DetermineDocumentsToSearchAsync( + protected override async Task DetermineDocumentsToSearchAsync( IMethodSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // First, find any documents with the full name of the accessor (i.e. get_Goo). // This will find explicit calls to the method (which can happen when C# references // a VB parameterized property). - var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); - var propertyDocuments = ImmutableArray.Empty; if (symbol.AssociatedSymbol is IPropertySymbol property && options.AssociatePropertyReferencesWithSpecificAccessor) { // we want to associate normal property references with the specific accessor being // referenced. So we also need to include documents with our property's name. Just // defer to the Property finder to find these docs and combine them with the result. - propertyDocuments = await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( + await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( property, globalAliases, project, documents, + processResult, processResultData, options with { AssociatePropertyReferencesWithSpecificAccessor = false }, cancellationToken).ConfigureAwait(false); } - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return documentsWithName.Concat(propertyDocuments, documentsWithGlobalAttributes); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } protected override async ValueTask> FindReferencesInDocumentAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 6d2acf8ebeb77..0f228294634e1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -91,30 +91,28 @@ private static void CascadeToPrimaryConstructorParameters(IPropertySymbol proper } } - protected sealed override async Task> DetermineDocumentsToSearchAsync( + protected sealed override async Task DetermineDocumentsToSearchAsync( IPropertySymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name).ConfigureAwait(false); - var forEachDocuments = IsForEachProperty(symbol) - ? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachProperty(symbol)) + await FindDocumentsWithForEachStatementsAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var elementAccessDocument = symbol.IsIndexer - ? await FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (symbol.IsIndexer) + await FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var indexerMemberCrefDocument = symbol.IsIndexer - ? await FindDocumentWithIndexerMemberCrefAsync(project, documents, cancellationToken).ConfigureAwait(false) - : []; + if (symbol.IsIndexer) + await FindDocumentWithIndexerMemberCrefAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var documentsWithGlobalAttributes = await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, cancellationToken).ConfigureAwait(false); - return ordinaryDocuments.Concat(forEachDocuments, elementAccessDocument, indexerMemberCrefDocument, documentsWithGlobalAttributes); + await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } private static bool IsForEachProperty(IPropertySymbol symbol) @@ -155,18 +153,18 @@ protected sealed override async ValueTask> FindRe return nameReferences.Concat(forEachReferences, indexerReferences, suppressionReferences); } - private static Task> FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( - Project project, IImmutableSet? documents, CancellationToken cancellationToken) + private static Task FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( + Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( - project, documents, static index => index.ContainsExplicitOrImplicitElementAccessExpression, cancellationToken); + project, documents, static index => index.ContainsExplicitOrImplicitElementAccessExpression, processResult, processResultData, cancellationToken); } - private static Task> FindDocumentWithIndexerMemberCrefAsync( - Project project, IImmutableSet? documents, CancellationToken cancellationToken) + private static Task FindDocumentWithIndexerMemberCrefAsync( + Project project, IImmutableSet? documents, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindDocumentsWithPredicateAsync( - project, documents, static index => index.ContainsIndexerMemberCref, cancellationToken); + project, documents, static index => index.ContainsIndexerMemberCref, processResult, processResultData, cancellationToken); } private static async Task> FindIndexerReferencesAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs index 32587f95c601e..6b1ffc51734be 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -14,11 +15,13 @@ internal sealed class TypeParameterSymbolReferenceFinder : AbstractTypeParameter protected override bool CanFind(ITypeParameterSymbol symbol) => symbol.TypeParameterKind != TypeParameterKind.Method; - protected override Task> DetermineDocumentsToSearchAsync( + protected override Task DetermineDocumentsToSearchAsync( ITypeParameterSymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -29,6 +32,6 @@ protected override Task> DetermineDocumentsToSearchAsyn // parameter has a different name in different parts that we won't find it. However, // this only happens in error situations. It is not legal in C# to use a different // name for a type parameter in different parts. - return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name, symbol.ContainingType.Name); + return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name, symbol.ContainingType.Name); } } From 0cf9b067a09775494aa03c7797ba84743dbc1ca8 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 19 Apr 2024 08:59:53 -0700 Subject: [PATCH 111/292] Add partial properties feature status (#73091) --- docs/Language Feature Status.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 3ba66cf3fe790..6a36d6df37b25 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -10,6 +10,7 @@ efforts behind them. | Feature | Branch | State | Developer | Reviewer | IDE Buddy | LDM Champ | | ------- | ------ | ----- | --------- | -------- | --------- | --------- | +| [Partial properties](https://github.com/dotnet/csharplang/issues/6420) | [partial-properties](https://github.com/dotnet/roslyn/tree/features/partial-properties) | [In Progress](https://github.com/dotnet/roslyn/issues/73090) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | TBD | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | | Ref/unsafe in iterators/async | [RefInAsync](https://github.com/dotnet/roslyn/tree/features/RefInAsync) | [In Progress](https://github.com/dotnet/roslyn/issues/72662) | [jjonescz](https://github.com/jjonescz) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | [ToddGrun](https://github.com/ToddGrun) | | | [Ref Struct Interfaces](https://github.com/dotnet/csharplang/issues/7608) | [RefStructInterfaces](https://github.com/dotnet/roslyn/tree/features/RefStructInterfaces) | [In Progress](https://github.com/dotnet/roslyn/issues/72124) | [AlekseyTs](https://github.com/AlekseyTs) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | [ToddGrun](https://github.com/ToddGrun) | [agocke](https://github.com/agocke), [jaredpar](https://github.com/jaredpar) | | [Semi-auto-properties](https://github.com/dotnet/csharplang/issues/140) | [semi-auto-props](https://github.com/dotnet/roslyn/tree/features/semi-auto-props) | [In Progress](https://github.com/dotnet/roslyn/issues/57012) | [Youssef1313](https://github.com/Youssef1313) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | From 3259fe9027b4201e4a42e1bdaa10194840f78706 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Fri, 19 Apr 2024 11:05:44 -0700 Subject: [PATCH 112/292] Keep entry in publishdata for 17.10 --- eng/config/PublishData.json | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index ee01121b84fd6..6c568c4301a1c 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -89,6 +89,7 @@ "Microsoft.VisualStudio.LanguageServices.LiveShare": "vs-impl", "Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient": "vs-impl", "Microsoft.CommonLanguageServerProtocol.Framework": "vs-impl", + "Microsoft.CommonLanguageServerProtocol.Framework.Binary": "vs-impl" } }, "comment-about-servicing-branches": "For a list of VS versions under servicing, see https://docs.microsoft.com/en-us/visualstudio/releases/2019/servicing#support-options-for-enterprise-and-professional-customers", From b543bdf26a95e464303ccf4f1ac5b0bd16cbacf0 Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Fri, 19 Apr 2024 11:20:09 -0700 Subject: [PATCH 113/292] Include file name in Contract failures (#73092) This adds the file names to the messages generated by contract failures. This information would've made it _significantly_ easier to debug a recent DDRIT failure. --- ...tionCompilationState.CompilationTracker.cs | 2 +- .../Compiler/Core/Utilities/Contract.cs | 48 ++++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index e0a5cd666f168..d38606df450db 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -1019,7 +1019,7 @@ private static void ValidateCompilationTreesMatchesProjectState(Compilation comp } /// - /// This is just the same as but throws a custom exception type to make this easier to find in telemetry since the exception type + /// This is just the same as but throws a custom exception type to make this easier to find in telemetry since the exception type /// is easily seen in telemetry. /// private static void ThrowExceptionIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs index 331ca8a3c5653..db14883bd1652 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/Contract.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Runtime.CompilerServices; namespace Roslyn.Utilities; @@ -22,11 +23,11 @@ internal static partial class Contract /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value, [CallerLineNumber] int lineNumber = 0) where T : class? + public static void ThrowIfNull([NotNull] T value, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) where T : class? { if (value is null) { - Fail("Unexpected null", lineNumber); + Fail("Unexpected null", lineNumber, filePath); } } @@ -35,11 +36,11 @@ public static void ThrowIfNull([NotNull] T value, [CallerLineNumber] int line /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T? value, [CallerLineNumber] int lineNumber = 0) where T : struct + public static void ThrowIfNull([NotNull] T? value, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) where T : struct { if (value is null) { - Fail("Unexpected null", lineNumber); + Fail("Unexpected null", lineNumber, filePath); } } @@ -48,11 +49,11 @@ public static void ThrowIfNull([NotNull] T? value, [CallerLineNumber] int lin /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value, string message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfNull([NotNull] T value, string message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (value is null) { - Fail(message, lineNumber); + Fail(message, lineNumber, filePath); } } @@ -61,11 +62,11 @@ public static void ThrowIfNull([NotNull] T value, string message, [CallerLine /// all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull([NotNull] T value, [InterpolatedStringHandlerArgument("value")] ThrowIfNullInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfNull([NotNull] T value, [InterpolatedStringHandlerArgument("value")] ThrowIfNullInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (value is null) { - Fail(message.GetFormattedText(), lineNumber); + Fail(message.GetFormattedText(), lineNumber, filePath); } } @@ -74,11 +75,11 @@ public static void ThrowIfNull([NotNull] T value, [InterpolatedStringHandlerA /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (!condition) { - Fail("Unexpected false", lineNumber); + Fail("Unexpected false", lineNumber, filePath); } } @@ -87,11 +88,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, string message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (!condition) { - Fail(message, lineNumber); + Fail(message, lineNumber, filePath); } } @@ -100,11 +101,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// in all builds /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfFalseInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfFalseInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (!condition) { - Fail(message.GetFormattedText(), lineNumber); + Fail(message.GetFormattedText(), lineNumber, filePath); } } @@ -113,11 +114,11 @@ public static void ThrowIfFalse([DoesNotReturnIf(parameterValue: false)] bool co /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (condition) { - Fail("Unexpected true", lineNumber); + Fail("Unexpected true", lineNumber, filePath); } } @@ -126,11 +127,11 @@ public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool cond /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, string message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, string message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (condition) { - Fail(message, lineNumber); + Fail(message, lineNumber, filePath); } } @@ -139,17 +140,20 @@ public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool cond /// all builds. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfTrueInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0) + public static void ThrowIfTrue([DoesNotReturnIf(parameterValue: true)] bool condition, [InterpolatedStringHandlerArgument("condition")] ThrowIfTrueInterpolatedStringHandler message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) { if (condition) { - Fail(message.GetFormattedText(), lineNumber); + Fail(message.GetFormattedText(), lineNumber, filePath); } } [DebuggerHidden] [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void Fail(string message = "Unexpected", [CallerLineNumber] int lineNumber = 0) - => throw new InvalidOperationException($"{message} - line {lineNumber}"); + public static void Fail(string message = "Unexpected", [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string? filePath = null) + { + var fileName = filePath is null ? null : Path.GetFileName(filePath); + throw new InvalidOperationException($"{message} - file {fileName} line {lineNumber}"); + } } From 6bec9a66884f9910e27a51e91de43df9556ca84e Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Fri, 19 Apr 2024 11:38:38 -0700 Subject: [PATCH 114/292] One diagnostic source provider per source name --- ...bstractDocumentDiagnosticSourceProvider.cs | 41 +++++ ...stractWorkspaceDiagnosticSourceProvider.cs | 149 +----------------- .../DiagnosticSourceManager.cs | 74 +++------ .../DocumentDiagnosticSource.cs | 1 - .../IDiagnosticSourceProvider.cs | 9 +- .../DocumentDiagnosticSourceProvider.cs | 131 --------------- ...EditAndContinueDiagnosticSourceProvider.cs | 30 ++++ ...ntaxAndSemanticDiagnosticSourceProvider.cs | 72 +++++++++ .../DocumentTaskDiagnosticSourceProvider.cs | 37 +++++ .../PublicDocumentDiagnosticSourceProvider.cs | 22 ++- ...ocumentNonLocalDiagnosticSourceProvider.cs | 43 +++++ .../PublicDocumentPullDiagnosticsHandler.cs | 8 +- ...PublicWorkspaceDiagnosticSourceProvider.cs | 5 +- .../PublicWorkspacePullDiagnosticsHandler.cs | 2 +- .../WorkspaceDiagnosticSourceProvider.cs | 23 --- ...mentsAndProjectDiagnosticSourceProvider.cs | 118 ++++++++++++++ ...EditAndContinueDiagnosticSourceProvider.cs | 32 ++++ .../WorkspaceTaskDiagnosticSourceProvider.cs | 50 ++++++ ...stractHotReloadDiagnosticSourceProvider.cs | 6 +- ...cumentHotReloadDiagnosticSourceProvider.cs | 4 +- ...kspaceHotReloadDiagnosticSourceProvider.cs | 4 +- 21 files changed, 480 insertions(+), 381 deletions(-) create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs create mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..f5f014ddbde07 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractDocumentDiagnosticSourceProvider(string name) : IDiagnosticSourceProvider +{ + public bool IsDocument => true; + public string Name => name; + + public abstract ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); + + protected static TextDocument? GetOpenDocument(RequestContext context) + { + // Note: context.Document may be null in the case where the client is asking about a document that we have + // since removed from the workspace. In this case, we don't really have anything to process. + // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. + // + // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each + // handler treats those as separate worlds that they are responsible for. + var textDocument = context.TextDocument; + if (textDocument is null) + { + context.TraceInformation("Ignoring diagnostics request because no text document was provided"); + return null; + } + + if (!context.IsTracking(textDocument.GetURI())) + { + context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); + return null; + } + + return textDocument; + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs index 1eedb04f10b46..473aecf7010d5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs @@ -2,80 +2,33 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.TaskList; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal abstract class AbstractWorkspaceDiagnosticSourceProvider( - IDiagnosticAnalyzerService diagnosticAnalyzerService, - IGlobalOptionService globalOptions, - ImmutableArray sourceNames) - : IDiagnosticSourceProvider +internal abstract class AbstractWorkspaceDiagnosticSourceProvider(string name) : IDiagnosticSourceProvider { public bool IsDocument => false; - public ImmutableArray SourceNames => sourceNames; + public string Name => name; - public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + public abstract ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); + + protected static bool ShouldIgnoreContext(RequestContext context) { // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for // document-diagnostics instead. - if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) - return []; - - if (sourceName == PullDiagnosticCategories.Task) - return GetTaskListDiagnosticSources(context, globalOptions); - - if (sourceName == PullDiagnosticCategories.EditAndContinue) - return await EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken).ConfigureAwait(false); - - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - if (sourceName == PullDiagnosticCategories.WorkspaceDocumentsAndProject) - return await GetDiagnosticSourcesAsync(context, globalOptions, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); - - // if it's a category we don't recognize, return nothing. - return []; - } - - private static ImmutableArray GetTaskListDiagnosticSources( - RequestContext context, IGlobalOptionService globalOptions) - { - Contract.ThrowIfNull(context.Solution); - - // Only compute task list items for closed files if the option is on for it. - var taskListEnabled = globalOptions.GetTaskListOptions().ComputeForClosedFiles; - if (!taskListEnabled) - return []; - - using var _ = ArrayBuilder.GetInstance(out var result); - - foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) - { - foreach (var document in project.Documents) - { - if (!ShouldSkipDocument(context, document)) - result.Add(new TaskListDiagnosticSource(document, globalOptions)); - } - } - - return result.ToImmutableAndClear(); + return context.ServerKind == WellKnownLspServerKinds.RazorLspServer; } - private static IEnumerable GetProjectsInPriorityOrder( + protected static IEnumerable GetProjectsInPriorityOrder( Solution solution, ImmutableArray supportedLanguages) { return GetProjectsInPriorityOrderWorker(solution) @@ -103,7 +56,7 @@ private static IEnumerable GetProjectsInPriorityOrder( } } - private static bool ShouldSkipDocument(RequestContext context, TextDocument document) + protected static bool ShouldSkipDocument(RequestContext context, TextDocument document) { // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). // Each handler treats those as separate worlds that they are responsible for. @@ -117,91 +70,5 @@ private static bool ShouldSkipDocument(RequestContext context, TextDocument docu // for any razor file they are interested in. return document.IsRazorDocument(); } - - /// - /// There are three potential sources for reporting workspace diagnostics: - /// - /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest - /// project snapshot and return up-to-date diagnostics computed from this analysis. - /// - /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly - /// triggered code analysis execution on either the current or a prior project snapshot, we return - /// diagnostics from this execution. These diagnostics may be stale with respect to the current - /// project snapshot, but they match user's intent of not enabling continuous background analysis - /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on - /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. - /// - /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. - /// - /// If full solution analysis is disabled AND code analysis was never executed for the given project, - /// we have no workspace diagnostics to report and bail out. - /// - public static async ValueTask> GetDiagnosticSourcesAsync( - RequestContext context, IGlobalOptionService globalOptions, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) - { - Contract.ThrowIfNull(context.Solution); - - using var _ = ArrayBuilder.GetInstance(out var result); - - var solution = context.Solution; - var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; - var codeAnalysisService = solution.Services.GetRequiredService(); - - foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) - await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); - - return result.ToImmutableAndClear(); - - async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) - { - var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); - if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) - return; - - Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled - ? ShouldIncludeAnalyzer : null; - - AddDocumentSources(project.Documents); - AddDocumentSources(project.AdditionalDocuments); - - // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. - if (enableDiagnosticsInSourceGeneratedFiles) - { - var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - AddDocumentSources(sourceGeneratedDocuments); - } - - // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. - AddProjectSource(); - - return; - - void AddDocumentSources(IEnumerable documents) - { - foreach (var document in documents) - { - if (!ShouldSkipDocument(context, document)) - { - // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. - var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) - : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); - result.Add(documentDiagnosticSource); - } - } - } - - void AddProjectSource() - { - var projectDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) - : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); - result.Add(projectDiagnosticSource); - } - - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) - => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; - } - } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 664bd043fa3af..7acc623d0e11d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -7,84 +7,52 @@ using System.Collections.Immutable; using System.Composition; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { [Export(typeof(DiagnosticSourceManager)), Shared] - internal class DiagnosticSourceManager : IDiagnosticSourceManager + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal class DiagnosticSourceManager([ImportMany] Lazy> sourceProviders) : IDiagnosticSourceManager { - private readonly Lazy> _sources; - private ImmutableDictionary>? _documentSources; - private ImmutableDictionary>? _workspaceSources; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DiagnosticSourceManager([ImportMany] Lazy> sources) - { - _sources = sources; - } + private ImmutableDictionary? _documentProviders; + private ImmutableDictionary? _workspaceProviders; /// public IEnumerable GetSourceNames(bool isDocument) { EnsureInitialized(); - return (isDocument ? _documentSources : _workspaceSources)!.Keys; + return (isDocument ? _documentProviders : _workspaceProviders)!.Keys; } /// - public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) { EnsureInitialized(); - var providersDictionary = isDocument ? _documentSources : _workspaceSources; - if (!providersDictionary!.TryGetValue(sourceName, out var providers)) - { - return []; - } + var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; + if (providersDictionary!.TryGetValue(sourceName, out var provider)) + return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); - using var _ = ArrayBuilder.GetInstance(out var builder); - foreach (var provider in providers) - { - var sources = await provider.CreateDiagnosticSourcesAsync(context, sourceName, cancellationToken).ConfigureAwait(false); - builder.AddRange(sources); - } - - return builder.ToImmutableAndClear(); + return new([]); } private void EnsureInitialized() { - if (_documentSources == null || _workspaceSources == null) + if (_documentProviders == null || _workspaceProviders == null) { - Dictionary> documentSources = new(); - Dictionary> workspaceSources = new(); - foreach (var source in _sources.Value) - { - var attribute = source.GetType().GetCustomAttributes(inherit: false).FirstOrDefault(); - if (attribute != null) - { - var scopedSources = source.IsDocument ? documentSources : workspaceSources; - foreach (var sourceName in source.SourceNames) - { - if (!scopedSources.TryGetValue(sourceName, out var sources)) - { - sources = new List(); - scopedSources[sourceName] = sources; - } - ((List)sources).Add(source); - } - } - } - - var immutableSources = documentSources.ToImmutableDictionary(entry => entry.Key, entry => entry.Value.ToImmutableArray()); - Interlocked.CompareExchange(ref _documentSources, immutableSources, null); - immutableSources = workspaceSources.ToImmutableDictionary(entry => entry.Key, entry => entry.Value.ToImmutableArray()); - Interlocked.CompareExchange(ref _workspaceSources, immutableSources, null); + var documentProviders = sourceProviders.Value + .Where(p => p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + Interlocked.CompareExchange(ref _documentProviders, documentProviders, null); + + var workspaceProviders = sourceProviders.Value + .Where(p => !p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + Interlocked.CompareExchange(ref _workspaceProviders, workspaceProviders, null); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 5cae97d9f3229..0a11ff5f1127f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs index 8c68d8b82094e..bcac55c7deb64 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceProvider.cs @@ -19,16 +19,15 @@ internal interface IDiagnosticSourceProvider bool IsDocument { get; } /// - /// Source names that this provider can provide. + /// Provider's name. Each should have a unique name within scope. /// - ImmutableArray SourceNames { get; } + string Name { get; } /// - /// Creates the diagnostic sources for the given . + /// Creates the diagnostic sources. /// /// The context. - /// Source name. /// The cancellation token. - ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken); + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs deleted file mode 100644 index e13833fe0dbe5..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentDiagnosticSourceProvider.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.SolutionCrawler; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -[ExportDiagnosticSourceProvider, Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class DocumentDiagnosticSourceProvider( - [Import] IGlobalOptionService globalOptions, - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : IDiagnosticSourceProvider -{ - private static readonly ImmutableArray sourceNames = - [ - PullDiagnosticCategories.Task, - PullDiagnosticCategories.DocumentCompilerSyntax, - PullDiagnosticCategories.DocumentCompilerSemantic, - PullDiagnosticCategories.DocumentAnalyzerSyntax, - PullDiagnosticCategories.DocumentAnalyzerSemantic - ]; - - public bool IsDocument => true; - public ImmutableArray SourceNames => sourceNames; - - public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) - { - if (sourceName == PullDiagnosticCategories.Task) - return new(GetDiagnosticSources(diagnosticAnalyzerService, diagnosticKind: default, nonLocalDocumentDiagnostics: false, taskList: true, context, globalOptions)); - - if (sourceName == PullDiagnosticCategories.EditAndContinue) - { - if (GetEditAndContinueDiagnosticSource(context) is IDiagnosticSource source) - { - return new([source]); - } - - return new([]); - } - - var diagnosticKind = sourceName switch - { - PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, - PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, - PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, - PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, - //// if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - //null => DiagnosticKind.All, // !!!VS Code does not request any diag kind!!! - // // if it's a category we don't recognize, return nothing. - _ => (DiagnosticKind?)null, - }; - - if (diagnosticKind is null) - return new([]); - - return new(GetDiagnosticSources(diagnosticAnalyzerService, diagnosticKind.Value, nonLocalDocumentDiagnostics: false, taskList: false, context, globalOptions)); - } - - internal static IDiagnosticSource? GetEditAndContinueDiagnosticSource(RequestContext context) - => context.GetTrackedDocument() is { } document ? EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document) : null; - - internal static ImmutableArray GetDiagnosticSources( - IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, bool nonLocalDocumentDiagnostics, bool taskList, RequestContext context, IGlobalOptionService globalOptions) - { - // For the single document case, that is the only doc we want to process. - // - // Note: context.Document may be null in the case where the client is asking about a document that we have - // since removed from the workspace. In this case, we don't really have anything to process. - // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. - // - // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each - // handler treats those as separate worlds that they are responsible for. - var textDocument = context.TextDocument; - if (textDocument is null) - { - context.TraceInformation("Ignoring diagnostics request because no text document was provided"); - return []; - } - - var document = textDocument as Document; - if (taskList && document is null) - { - context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); - return []; - } - - if (!context.IsTracking(textDocument.GetURI())) - { - context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); - return []; - } - - if (nonLocalDocumentDiagnostics) - return GetNonLocalDiagnosticSources(); - - return taskList - ? [new TaskListDiagnosticSource(document!, globalOptions)] - : [new DocumentDiagnosticSource(diagnosticAnalyzerService, diagnosticKind, textDocument)/*, Xaml source might go here; ???why doc while it should we workspace???*/]; - - ImmutableArray GetNonLocalDiagnosticSources() - { - Debug.Assert(!taskList); - - // This code path is currently only invoked from the public LSP handler, which always uses 'DiagnosticKind.All' - Debug.Assert(diagnosticKind == DiagnosticKind.All); - - // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) - return []; - - return [new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]; - - // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); - } - } -} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..52aa70dcab7a0 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DocumentEditAndContinueDiagnosticSourceProvider() + : AbstractDocumentDiagnosticSourceProvider(PullDiagnosticCategories.EditAndContinue) +{ + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is not Document document) + return new([]); + + var source = EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document); + return new([source]); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..d937791b2aeff --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider : AbstractDocumentDiagnosticSourceProvider +{ + private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; + private readonly DiagnosticKind _kind; + + public AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(IDiagnosticAnalyzerService diagnosticAnalyzerService, + DiagnosticKind kind, string sourceName) : base(sourceName) + { + _diagnosticAnalyzerService = diagnosticAnalyzerService; + _kind = kind; + } + + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (GetOpenDocument(context) is not TextDocument textDocument) + return new([]); + + var source = new DocumentDiagnosticSource(_diagnosticAnalyzerService, _kind, textDocument); + return new([source]); + } + + [ExportDiagnosticSourceProvider, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSyntax) + { + } + + [ExportDiagnosticSourceProvider, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSemantic) + { + } + + [ExportDiagnosticSourceProvider, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.AnalyzerSemantic, PullDiagnosticCategories.DocumentAnalyzerSemantic) + { + } + + [ExportDiagnosticSourceProvider, Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + DiagnosticKind.AnalyzerSyntax, PullDiagnosticCategories.DocumentAnalyzerSyntax) + { + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..8a343fabb4899 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) + : AbstractDocumentDiagnosticSourceProvider(PullDiagnosticCategories.Task) +{ + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (GetOpenDocument(context) is not TextDocument textDocument) + return new([]); + + if (textDocument is not Document document) + { + context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); + return new([]); + } + + var source = new TaskListDiagnosticSource(document, globalOptions); + return new([source]); + } +} + diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs index 1ef1c07135337..0880b465d18ae 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -18,19 +17,18 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicDocumentDiagnosticSourceProvider( - [Import] IGlobalOptionService globalOptions, - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : IDiagnosticSourceProvider + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentDiagnosticSourceProvider(All) { - public const string NonLocalSource = "nonLocal_B69807DB-28FB-4846-884A-1152E54C8B62"; - private static readonly ImmutableArray sourceNames = [NonLocalSource]; + public const string All = "All_B69807DB-28FB-4846-884A-1152E54C8B62"; - public bool IsDocument => true; - public ImmutableArray SourceNames => sourceNames; - - public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - var nonLocalDocumentDiagnostics = sourceName == NonLocalSource; - var result = DocumentDiagnosticSourceProvider.GetDiagnosticSources(diagnosticAnalyzerService, DiagnosticKind.All, nonLocalDocumentDiagnostics, taskList: false, context, globalOptions); - return new(result); + var textDocument = AbstractDocumentDiagnosticSourceProvider.GetOpenDocument(context); + if (textDocument is null) + return new([]); + + var source = new DocumentDiagnosticSource(diagnosticAnalyzerService, DiagnosticKind.All /* IS THIS RIGHT ???*/, textDocument); + return new([source]); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..171fa9bb9bd55 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.SolutionCrawler; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( + [Import] IGlobalOptionService globalOptions, + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + : AbstractDocumentDiagnosticSourceProvider(NonLocal) +{ + public const string NonLocal = "NonLocal_B69807DB-28FB-4846-884A-1152E54C8B62"; + + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + var textDocument = GetOpenDocument(context); + if (textDocument is null) + return new([]); + + // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. + if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + return new([]); + + return new([new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]); + + // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 9e1f3cfdd33c7..7600bc2f1ee7d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -93,10 +93,10 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi protected override ValueTask> GetOrderedDiagnosticSourcesAsync(DocumentDiagnosticParams diagnosticParams, RequestContext context, CancellationToken cancellationToken) { - var sourceName = diagnosticParams.Identifier == DocumentNonLocalDiagnosticIdentifier.ToString() - ? PublicDocumentDiagnosticSourceProvider.NonLocalSource - : string.Empty; - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); + if (diagnosticParams.Identifier is string sourceName) + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); + + return new([]); } protected override ImmutableArray? GetPreviousResults(DocumentDiagnosticParams diagnosticsParams) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs index d806d8e235ea0..c7e98b6187713 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs @@ -11,12 +11,15 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; +// FIGURE OUT WHAT IT IS SUPPOSED TO DO +/* [ExportDiagnosticSourceProvider, Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicWorkspaceDiagnosticSourceProvider( [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, [Import] IGlobalOptionService globalOptions) - : AbstractWorkspaceDiagnosticSourceProvider(diagnosticAnalyzerService, globalOptions, [""]) + : AbstractWorkspaceDiagnosticSourceProvider("") { } +*/ diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 6c72098ffaf41..013182d9e5bc3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; using WorkspaceDiagnosticPartialReport = SumType; [Method(Methods.WorkspaceDiagnosticName)] -internal sealed partial class PublicWorkspacePullDiagnosticsHandler: AbstractWorkspacePullDiagnosticsHandler, IDisposable +internal sealed partial class PublicWorkspacePullDiagnosticsHandler : AbstractWorkspacePullDiagnosticsHandler, IDisposable { private readonly IClientLanguageServerManager _clientLanguageServerManager; public PublicWorkspacePullDiagnosticsHandler( diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs deleted file mode 100644 index ca21d6c822fee..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDiagnosticSourceProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -[ExportDiagnosticSourceProvider, Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class WorkspaceDiagnosticSourceProvider( - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, - [Import] IGlobalOptionService globalOptions) - : AbstractWorkspaceDiagnosticSourceProvider(diagnosticAnalyzerService, globalOptions, - [PullDiagnosticCategories.EditAndContinue, PullDiagnosticCategories.WorkspaceDocumentsAndProject]) -{ -} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..8c36d0905616c --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( + [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, + [Import] IGlobalOptionService globalOptions) + : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.WorkspaceDocumentsAndProject) +{ + /// + /// There are three potential sources for reporting workspace diagnostics: + /// + /// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest + /// project snapshot and return up-to-date diagnostics computed from this analysis. + /// + /// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly + /// triggered code analysis execution on either the current or a prior project snapshot, we return + /// diagnostics from this execution. These diagnostics may be stale with respect to the current + /// project snapshot, but they match user's intent of not enabling continuous background analysis + /// for always having up-to-date workspace diagnostics, but instead computing them explicitly on + /// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution. + /// + /// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document. + /// + /// If full solution analysis is disabled AND code analysis was never executed for the given project, + /// we have no workspace diagnostics to report and bail out. + /// + public override async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (!ShouldIgnoreContext(context)) + { + Contract.ThrowIfNull(context.Solution); + + using var _ = ArrayBuilder.GetInstance(out var result); + + var solution = context.Solution; + var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; + var codeAnalysisService = solution.Services.GetRequiredService(); + + foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) + await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + + return result.ToImmutableAndClear(); + + async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); + if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) + return; + + Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled + ? ShouldIncludeAnalyzer : null; + + AddDocumentSources(project.Documents); + AddDocumentSources(project.AdditionalDocuments); + + // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. + if (enableDiagnosticsInSourceGeneratedFiles) + { + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + AddDocumentSources(sourceGeneratedDocuments); + } + + // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. + AddProjectSource(); + + return; + + void AddDocumentSources(IEnumerable documents) + { + foreach (var document in documents) + { + if (!ShouldSkipDocument(context, document)) + { + // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. + var documentDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); + result.Add(documentDiagnosticSource); + } + } + } + + void AddProjectSource() + { + var projectDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); + result.Add(projectDiagnosticSource); + } + + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) + => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + } + } + + return []; + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..8869ef4ccccfa --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.EditAndContinue) +{ + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (!ShouldIgnoreContext(context)) + { + Contract.ThrowIfNull(context.Solution); + return EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken); + } + + return new([]); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..c5c18f4163c3f --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.TaskList; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) + : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.Task) +{ + public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (!ShouldIgnoreContext(context)) + { + Contract.ThrowIfNull(context.Solution); + + // Only compute task list items for closed files if the option is on for it. + if (globalOptions.GetTaskListOptions().ComputeForClosedFiles) + { + using var _ = ArrayBuilder.GetInstance(out var result); + foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) + { + foreach (var document in project.Documents) + { + if (!ShouldSkipDocument(context, document)) + result.Add(new TaskListDiagnosticSource(document, globalOptions)); + } + } + + return new(result.ToImmutableAndClear()); + } + } + + return new([]); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs index 7c6a0e6915c9d..cd0a235e52fa5 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs @@ -13,12 +13,10 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; internal class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider { - internal const string SourceName = "HotReloadDiagnostic"; - internal static readonly ImmutableArray SourceNames = [SourceName]; + string IDiagnosticSourceProvider.Name => "HotReloadDiagnostic"; - ImmutableArray IDiagnosticSourceProvider.SourceNames => SourceNames; bool IDiagnosticSourceProvider.IsDocument => throw new NotImplementedException(); - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) => throw new NotImplementedException(); } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs index 46f3f36252622..9fe8c155f3282 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs @@ -24,14 +24,12 @@ internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticMan { bool IDiagnosticSourceProvider.IsDocument => true; - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { if (context.GetTrackedDocument() is { } textDocument) { if (hotReloadErrorService.Errors.FirstOrDefault(e => e.DocumentId == textDocument.Id) is { } documentErrors) - { return new([new HotReloadDiagnosticSource(textDocument, documentErrors.Errors)]); - } } return new([]); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs index defca19ba4f9b..c2a08d6fed799 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs @@ -24,9 +24,9 @@ internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticMa : AbstractHotReloadDiagnosticSourceProvider , IDiagnosticSourceProvider { - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, CancellationToken cancellationToken) + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (sourceName != SourceName || context.Solution is not Solution solution) + if (context.Solution is not Solution solution) { return new([]); } From e3015603ce1733238891eaf829014db239dc1b2e Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Fri, 19 Apr 2024 11:49:59 -0700 Subject: [PATCH 115/292] Tweaks --- .../DiagnosticSourceManager.cs | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 7acc623d0e11d..8d19ed304909e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -15,45 +15,36 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { [Export(typeof(DiagnosticSourceManager)), Shared] - [method: ImportingConstructor] - [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal class DiagnosticSourceManager([ImportMany] Lazy> sourceProviders) : IDiagnosticSourceManager + internal class DiagnosticSourceManager : IDiagnosticSourceManager { - private ImmutableDictionary? _documentProviders; - private ImmutableDictionary? _workspaceProviders; + private readonly Lazy> _documentProviders; + private readonly Lazy> _workspaceProviders; - /// - public IEnumerable GetSourceNames(bool isDocument) + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DiagnosticSourceManager([ImportMany] Lazy> sourceProviders) { - EnsureInitialized(); - return (isDocument ? _documentProviders : _workspaceProviders)!.Keys; + _documentProviders = new(() => sourceProviders.Value + .Where(p => p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp)); + + _workspaceProviders = new(() => sourceProviders.Value + .Where(p => !p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp)); } + /// + public IEnumerable GetSourceNames(bool isDocument) + => (isDocument ? _documentProviders : _workspaceProviders).Value.Keys; + /// public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) { - EnsureInitialized(); var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; - if (providersDictionary!.TryGetValue(sourceName, out var provider)) + if (providersDictionary.Value.TryGetValue(sourceName, out var provider)) return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); return new([]); } - - private void EnsureInitialized() - { - if (_documentProviders == null || _workspaceProviders == null) - { - var documentProviders = sourceProviders.Value - .Where(p => p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); - Interlocked.CompareExchange(ref _documentProviders, documentProviders, null); - - var workspaceProviders = sourceProviders.Value - .Where(p => !p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); - Interlocked.CompareExchange(ref _workspaceProviders, workspaceProviders, null); - } - } } } From 76680ae68452bf825b14c052008c529f24e836dc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 12:03:41 -0700 Subject: [PATCH 116/292] Yield properly --- .../Core.Wpf/Suggestions/SuggestedActionsSource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index af3f792840d2a..f4f2be82f379a 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -211,7 +211,7 @@ private void OnTextViewClosed(object sender, EventArgs e) async Task GetFixLevelAsync() { // Ensure we yield the thread that called into us, allowing it to continue onwards. - await TaskScheduler.Default; + await TaskScheduler.Default.SwitchTo(alwaysYield: true); var lowPriorityAnalyzers = new ConcurrentSet(); foreach (var order in Orderings) @@ -258,7 +258,7 @@ private void OnTextViewClosed(object sender, EventArgs e) async Task TryGetRefactoringSuggestedActionCategoryAsync(TextSpan? selection) { // Ensure we yield the thread that called into us, allowing it to continue onwards. - await TaskScheduler.Default; + await TaskScheduler.Default.SwitchTo(alwaysYield: true); if (!selection.HasValue) { From ff07e9796ed17de99b76c18053432077c8df57e1 Mon Sep 17 00:00:00 2001 From: Jonathan Yi <101259035+jonathanjyi@users.noreply.github.com> Date: Fri, 19 Apr 2024 19:13:11 +0000 Subject: [PATCH 117/292] updated Microsoft.VisualStudio.Telemetry to 17.11.8 and Microsoft.VisualStudio.Utilities.Internal to 16.3.73 --- eng/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 97ff84ec7abe2..d9970c400f1f0 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -103,14 +103,14 @@ - + - + From facabce369358a84d1d95718ac9d6fa0021aad27 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 12:21:29 -0700 Subject: [PATCH 118/292] Use extension to simplify pattern of reading from a channel --- src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index ca05cccdec70f..a7a271ff2ae70 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Serialization; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Remote; @@ -150,14 +151,11 @@ private async Task ReadAssetsFromChannelAndWriteToPipeAsync(ChannelReader Date: Fri, 19 Apr 2024 12:35:32 -0700 Subject: [PATCH 119/292] Increase mmf file sizes based on emperical data for roslyn itself. --- .../TemporaryStorageServiceFactory.cs | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index 451e15a091a56..259fa7263c73c 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -29,22 +29,23 @@ namespace Microsoft.CodeAnalysis.Host; internal sealed partial class TemporaryStorageService : ITemporaryStorageServiceInternal { /// - /// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other - /// storage units. + /// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other storage + /// units. /// /// - /// This value was arbitrarily chosen and appears to work well. Can be changed if data suggests - /// something better. + /// The value of 256k reduced the number of files dumped to separate memory mapped files by 60% compared to + /// the next lower power-of-2 size for Roslyn.sln itself. /// /// - private const long SingleFileThreshold = 128 * 1024; + private const long SingleFileThreshold = 256 * 1024; /// /// The size in bytes of a memory mapped file created to store multiple temporary objects. /// /// - /// This value was arbitrarily chosen and appears to work well. Can be changed if data suggests - /// something better. + /// This value (8mb) creates roughly 35 memory mapped files (around 300MB) to store the contents of all of + /// Roslyn.sln a snapshot. This keeps the data safe, so that we can drop it from memory when not needed, but + /// reconstitute the contents we originally had in the snapshot in case the original files change on disk. /// /// private const long MultiFileBlockSize = SingleFileThreshold * 32; @@ -65,33 +66,24 @@ internal sealed partial class TemporaryStorageService : ITemporaryStorageService /// /// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer - /// allocation until space is no longer available in it. + /// allocation until space is no longer available in it. Access should be synchronized on /// - /// - /// Access should be synchronized on . - /// private ReferenceCountedDisposable.WeakReference _weakFileReference; - /// The name of the current memory mapped file for multiple storage units. - /// - /// Access should be synchronized on . - /// + /// The name of the current memory mapped file for multiple storage units. Access should be synchronized on + /// /// private string? _name; - /// The total size of the current memory mapped file for multiple storage units. - /// - /// Access should be synchronized on . - /// + /// The total size of the current memory mapped file for multiple storage units. Access should be + /// synchronized on /// private long _fileSize; /// - /// The offset into the current memory mapped file where the next storage unit can be held. + /// The offset into the current memory mapped file where the next storage unit can be held. Access should be + /// synchronized on . /// - /// - /// Access should be synchronized on . - /// /// private long _offset; From 8fb9484b47b2b87a314fa60a10b32ddc1999faae Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 12:47:13 -0700 Subject: [PATCH 120/292] Remove unused async entrypoints --- .../TemporaryStorageServiceFactory.cs | 53 ++++--------------- .../TemporaryStorage/ITemporaryStorage.cs | 2 - .../TemporaryStorageServiceTests.cs | 24 ++++----- 3 files changed, 20 insertions(+), 59 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index 259fa7263c73c..4fa4e5cf53697 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -357,28 +357,10 @@ public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) } } - public Task ReadStreamAsync(CancellationToken cancellationToken = default) - { - // See commentary in ReadTextAsync for why this is implemented this way. - return Task.Factory.StartNew(() => ReadStream(cancellationToken), cancellationToken, TaskCreationOptions.None, TaskScheduler.Default); - } - - public void WriteStream(Stream stream, CancellationToken cancellationToken = default) - { - // The Wait() here will not actually block, since with useAsync: false, the - // entire operation will already be done when WaitStreamMaybeAsync completes. - WriteStreamMaybeAsync(stream, useAsync: false, cancellationToken: cancellationToken).GetAwaiter().GetResult(); - } - - public Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken = default) - => WriteStreamMaybeAsync(stream, useAsync: true, cancellationToken: cancellationToken); - - private async Task WriteStreamMaybeAsync(Stream stream, bool useAsync, CancellationToken cancellationToken) + public void WriteStream(Stream stream, CancellationToken cancellationToken) { if (_memoryMappedInfo != null) - { throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) { @@ -386,32 +368,15 @@ private async Task WriteStreamMaybeAsync(Stream stream, bool useAsync, Cancellat _memoryMappedInfo = _service.CreateTemporaryStorage(size); using var viewStream = _memoryMappedInfo.CreateWritableStream(); - var buffer = SharedPools.ByteArray.Allocate(); - try + using var pooledObject = SharedPools.ByteArray.GetPooledObject(); + var buffer = pooledObject.Object; + while (true) { - while (true) - { - int count; - if (useAsync) - { - count = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); - } - else - { - count = stream.Read(buffer, 0, buffer.Length); - } - - if (count == 0) - { - break; - } - - viewStream.Write(buffer, 0, count); - } - } - finally - { - SharedPools.ByteArray.Free(buffer); + var count = stream.Read(buffer, 0, buffer.Length); + if (count == 0) + break; + + viewStream.Write(buffer, 0, count); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index f1848b42c8618..ef647f1af7b36 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -44,7 +44,5 @@ internal interface ITemporaryTextStorageInternal : IDisposable internal interface ITemporaryStreamStorageInternal : IDisposable { Stream ReadStream(CancellationToken cancellationToken = default); - Task ReadStreamAsync(CancellationToken cancellationToken = default); void WriteStream(Stream stream, CancellationToken cancellationToken = default); - Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken = default); } diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index bba69b5e4b499..df9924ce8f49a 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -60,8 +60,8 @@ public void TestTemporaryStorageStream() } data.Position = 0; - temporaryStorage.WriteStreamAsync(data).Wait(); - using var result = temporaryStorage.ReadStreamAsync().Result; + temporaryStorage.WriteStream(data, CancellationToken.None); + using var result = temporaryStorage.ReadStream(CancellationToken.None); Assert.Equal(data.Length, result.Length); for (var i = 0; i < SharedPools.ByteBufferSize; i++) @@ -119,18 +119,16 @@ public void TestTemporaryStreamStorageExceptions() // Nothing has been written yet Assert.Throws(() => storage.ReadStream(CancellationToken.None)); - Assert.Throws(() => storage.ReadStreamAsync().Result); // write a normal stream var stream = new MemoryStream(); stream.Write([42], 0, 1); stream.Position = 0; - storage.WriteStreamAsync(stream).Wait(); + storage.WriteStream(stream, CancellationToken.None); // Writing multiple times is not allowed // These should also throw before ever getting to the point where they would look at the null stream arg. - Assert.Throws(() => storage.WriteStream(null!)); - Assert.Throws(() => storage.WriteStreamAsync(null!).Wait()); + Assert.Throws(() => storage.WriteStream(null!, CancellationToken.None)); } [ConditionalFact(typeof(WindowsOnly))] @@ -144,7 +142,7 @@ public void TestZeroLengthStreams() // 0 length streams are allowed using (var stream1 = new MemoryStream()) { - storage.WriteStream(stream1); + storage.WriteStream(stream1, CancellationToken.None); } using (var stream2 = storage.ReadStream(CancellationToken.None)) @@ -176,7 +174,7 @@ public void TestTemporaryStorageMemoryMappedFileManagement() storage1.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1)); storage2.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i)); - storage3.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1)); + storage3.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); await Task.Yield(); @@ -220,12 +218,12 @@ public void TestTemporaryStorageScaling() var s = service.CreateTemporaryStreamStorage(); storageHandles.Add(s); data.Position = 0; - s.WriteStreamAsync(data).Wait(); + s.WriteStream(data, CancellationToken.None); } for (var i = 0; i < 1024 * 5; i++) { - using var s = storageHandles[i].ReadStreamAsync().Result; + using var s = storageHandles[i].ReadStream(CancellationToken.None); Assert.Equal(1, s.ReadByte()); storageHandles[i].Dispose(); } @@ -247,7 +245,7 @@ public void StreamTest1() } expected.Position = 0; - storage.WriteStream(expected); + storage.WriteStream(expected, CancellationToken.None); expected.Position = 0; using var stream = storage.ReadStream(CancellationToken.None); @@ -274,7 +272,7 @@ public void StreamTest2() } expected.Position = 0; - storage.WriteStream(expected); + storage.WriteStream(expected, CancellationToken.None); expected.Position = 0; using var stream = storage.ReadStream(CancellationToken.None); @@ -316,7 +314,7 @@ public void StreamTest3() } expected.Position = 0; - storage.WriteStream(expected); + storage.WriteStream(expected, CancellationToken.None); expected.Position = 0; using var stream = storage.ReadStream(CancellationToken.None); From 03db600b62bdc3e9c2dc33734d77d5d36e2dd66d Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Fri, 19 Apr 2024 14:34:10 -0700 Subject: [PATCH 121/292] Watch razor and cshtml files for devkit (#73077) DevKit does not get a loaded project, so we need to watch for razor/cshtml file changes manually in the ProjectSystemProject. --- .../ProjectSystem/ProjectSystemProject.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index 6249e92df4226..4f387f594bed4 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -191,21 +191,30 @@ internal ProjectSystemProject( _filePath = filePath; _parseOptions = parseOptions; - var fileExtensionToWatch = language switch { LanguageNames.CSharp => ".cs", LanguageNames.VisualBasic => ".vb", _ => null }; + var watchedDirectories = GetWatchedDirectories(language, filePath); + _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(watchedDirectories); + _documentFileChangeContext.FileChanged += DocumentFileChangeContext_FileChanged; - if (filePath != null && fileExtensionToWatch != null) - { - // Since we have a project directory, we'll just watch all the files under that path; that'll avoid extra overhead of - // having to add explicit file watches everywhere. - var projectDirectoryToWatch = new WatchedDirectory(Path.GetDirectoryName(filePath)!, fileExtensionToWatch); - _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(projectDirectoryToWatch); - } - else + static WatchedDirectory[] GetWatchedDirectories(string? language, string? filePath) { - _documentFileChangeContext = _projectSystemProjectFactory.FileChangeWatcher.CreateContext(); - } + if (filePath is null) + { + return []; + } - _documentFileChangeContext.FileChanged += DocumentFileChangeContext_FileChanged; + var rootPath = Path.GetDirectoryName(filePath); + if (rootPath is null) + { + return []; + } + + return language switch + { + LanguageNames.VisualBasic => [new(rootPath, ".vb")], + LanguageNames.CSharp => [new(rootPath, ".cs"), new(rootPath, ".razor"), new(rootPath, ".cshtml")], + _ => [] + }; + } } private void ChangeProjectProperty(ref T field, T newValue, Func updateSolution, bool logThrowAwayTelemetry = false) From 72398f0cd5498f9ea9991d3b78b1bf2be4bce0cb Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Fri, 19 Apr 2024 14:45:07 -0700 Subject: [PATCH 122/292] Refining HotReloadDiagnostics API --- .../DiagnosticSourceManager.cs | 6 +- .../Contracts/IHotReloadDiagnosticManager.cs | 19 +++-- .../Contracts/IHotReloadDiagnosticSource.cs | 26 +++++++ ...cumentHotReloadDiagnosticSourceProvider.cs | 12 ++- .../Internal/HotReloadDiagnosticManager.cs | 78 +++---------------- .../Internal/HotReloadDiagnosticSource.cs | 13 ++-- ...kspaceHotReloadDiagnosticSourceProvider.cs | 17 ++-- .../InternalAPI.Unshipped.txt | 14 ++-- 8 files changed, 86 insertions(+), 99 deletions(-) create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 8d19ed304909e..1db563fed292b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -14,16 +14,18 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { - [Export(typeof(DiagnosticSourceManager)), Shared] + [Export(typeof(IDiagnosticSourceManager)), Shared] internal class DiagnosticSourceManager : IDiagnosticSourceManager { private readonly Lazy> _documentProviders; private readonly Lazy> _workspaceProviders; + private readonly Lazy> sourceProviders; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DiagnosticSourceManager([ImportMany] Lazy> sourceProviders) + public DiagnosticSourceManager(/*[ImportMany] Lazy> sourceProviders*/) { + sourceProviders = new(() => new List()); _documentProviders = new(() => sourceProviders.Value .Where(p => p.IsDocument) .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp)); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs index aa48b2ea23197..3e601bb176a98 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs @@ -9,20 +9,23 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts internal interface IHotReloadDiagnosticManager { /// - /// Hot reload errors. + /// Hot reload diagnostics for all sources. /// - ImmutableArray Errors { get; } + ImmutableArray Sources { get; } /// - /// Update the diagnostics for the given group name. + /// Registers source of hot reload diagnostics. /// - /// The diagnostics. - /// The group name. - void UpdateErrors(ImmutableArray errors, string groupName); + void Register(IHotReloadDiagnosticSource source); /// - /// Clears all errors. + /// Unregisters source of hot reload diagnostics. /// - void Clear(); + void Unregister(IHotReloadDiagnosticSource source); + + /// + /// Requests refresh of hot reload diagnostics. + /// + void Refresh(); } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs new file mode 100644 index 0000000000000..661ecc1f05724 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +{ + /// + /// Source for hot reload diagnostics. + /// + internal interface IHotReloadDiagnosticSource + { + /// + /// Provides list of document ids that have hot reload diagnostics. + /// + ValueTask> GetDocumentIdsAsync(CancellationToken cancellationToken); + + /// + /// Provides list of diagnostics for the given document. + /// + ValueTask> GetDocumentDiagnosticsAsync(TextDocument document, CancellationToken cancellationToken); + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs index 9fe8c155f3282..16d9e109d4741 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; @@ -18,7 +19,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; [ExportDiagnosticSourceProvider, Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadErrorService) +internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadDiagnosticManager) : AbstractHotReloadDiagnosticSourceProvider , IDiagnosticSourceProvider { @@ -28,8 +29,13 @@ ValueTask> IDiagnosticSourceProvider.CreateDia { if (context.GetTrackedDocument() is { } textDocument) { - if (hotReloadErrorService.Errors.FirstOrDefault(e => e.DocumentId == textDocument.Id) is { } documentErrors) - return new([new HotReloadDiagnosticSource(textDocument, documentErrors.Errors)]); + List sources = new(); + foreach (var hotReloadSource in hotReloadDiagnosticManager.Sources) + { + sources.Add(new HotReloadDiagnosticSource(textDocument, hotReloadSource)); + } + + return new(sources.ToImmutableArray()); } return new([]); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs index 14b54af628827..cdd278715f947 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -3,13 +3,11 @@ // See the LICENSE file in the project root for more information. using System; -using System.Linq; using System.Collections.Immutable; using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; using Microsoft.CodeAnalysis.Host.Mef; -using System.Collections.Generic; namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; @@ -18,78 +16,24 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class HotReloadDiagnosticManager(IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager { - private ImmutableDictionary> _errors = ImmutableDictionary>.Empty; - private ImmutableArray? _allErrors = null; + private ImmutableArray _sources = ImmutableArray.Empty; - void IHotReloadDiagnosticManager.UpdateErrors(ImmutableArray errors, string groupName) - { - errors = errors.RemoveAll(d => d.Errors.IsEmpty); + ImmutableArray IHotReloadDiagnosticManager.Sources => _sources; + void IHotReloadDiagnosticManager.Refresh() => diagnosticsRefresher.RequestWorkspaceRefresh(); - var oldErrors = _errors; - if (errors.IsEmpty) - { - _errors = _errors.Remove(groupName); - } - else - { - _errors = _errors.SetItem(groupName, errors); - } - - if (_errors != oldErrors) - { - _allErrors = null; - diagnosticsRefresher.RequestWorkspaceRefresh(); - } - } - - void IHotReloadDiagnosticManager.Clear() + void IHotReloadDiagnosticManager.Register(IHotReloadDiagnosticSource source) { - if (!_errors.IsEmpty) + // We use array instead of e.g. HashSet because we expect the number of sources to be small. Usually 1. + if (!_sources.Contains(source)) { - _errors = ImmutableDictionary>.Empty; - _allErrors = ImmutableArray.Empty; - diagnosticsRefresher.RequestWorkspaceRefresh(); + _sources = _sources.Add(source); } } - ImmutableArray IHotReloadDiagnosticManager.Errors + void IHotReloadDiagnosticManager.Unregister(IHotReloadDiagnosticSource source) { - get - { - _allErrors ??= ComputeAllErrors(_errors); - return _allErrors.Value; - } - } - - private static ImmutableArray ComputeAllErrors(ImmutableDictionary> errors) - { - if (errors.Count == 0) - { - return ImmutableArray.Empty; - } - - if (errors.Count == 1) - { - return errors.First().Value; - } - - var allErrors = new Dictionary>(); - foreach (var group in errors.Values) - { - foreach (var documentErrors in group) - { - if (!allErrors.TryGetValue(documentErrors.DocumentId, out var list)) - { - list = new List(); - allErrors.Add(documentErrors.DocumentId, list); - } - - list.AddRange(documentErrors.Errors); - } - } - - return allErrors - .Where(kvp => kvp.Value.Count > 0) - .Select(kvp => new HotReloadDocumentDiagnostics(kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableArray(); + // We use array instead of e.g. HashSet because we expect the number of sources to be small. Usually 1. + _sources = _sources.Remove(source); + diagnosticsRefresher.RequestWorkspaceRefresh(); } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs index c11487fd93070..5d41c5a98860e 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -2,27 +2,26 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; using Roslyn.LanguageServer.Protocol; -using Microsoft.CodeAnalysis.LanguageServer; namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal { - internal class HotReloadDiagnosticSource(TextDocument document, ImmutableArray errors) : IDiagnosticSource + internal class HotReloadDiagnosticSource(TextDocument document, IHotReloadDiagnosticSource hotReloadDiagnosticSource) : IDiagnosticSource { - Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { - var result = errors.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); - return Task.FromResult(result); + var diagnostics = await hotReloadDiagnosticSource.GetDocumentDiagnosticsAsync(document, cancellationToken).ConfigureAwait(false); + var result = diagnostics.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); + return result; } TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs index c2a08d6fed799..742fba39407f5 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs @@ -24,24 +24,27 @@ internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticMa : AbstractHotReloadDiagnosticSourceProvider , IDiagnosticSourceProvider { - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + async ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { if (context.Solution is not Solution solution) { - return new([]); + return []; } using var _ = ArrayBuilder.GetInstance(out var builder); - foreach (var documentErrors in hotReloadErrorService.Errors) + foreach (var hotReloadSource in hotReloadErrorService.Sources) { - TextDocument? document = solution.GetAdditionalDocument(documentErrors.DocumentId) ?? solution.GetDocument(documentErrors.DocumentId); - if (document != null && !context.IsTracking(document.GetURI())) + var docIds = await hotReloadSource.GetDocumentIdsAsync(cancellationToken).ConfigureAwait(false); + foreach (var docId in docIds) { - builder.Add(new HotReloadDiagnosticSource(document, documentErrors.Errors)); + if (solution.GetDocument(docId) is { } document && !context.IsTracking(document.GetURI())) + { + builder.Add(new HotReloadDiagnosticSource(document, hotReloadSource)); + } } } var result = builder.ToImmutableAndClear(); - return new(result); + return result; } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 9deff7bbdd06d..439b114134052 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -4,9 +4,13 @@ Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocum Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.Errors.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.HotReloadDocumentDiagnostics(Microsoft.CodeAnalysis.DocumentId! documentId, System.Collections.Immutable.ImmutableArray errors) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Clear() -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Errors.get -> System.Collections.Immutable.ImmutableArray -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.UpdateErrors(System.Collections.Immutable.ImmutableArray errors, string! groupName) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Refresh() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Register(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Sources.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Unregister(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDocumentDiagnosticsAsync(Microsoft.CodeAnalysis.TextDocument! document, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDocumentIdsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStartAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStopAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! @@ -15,11 +19,11 @@ Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnos Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.AbstractHotReloadDiagnosticSourceProvider() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadErrorService) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadDiagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.HotReloadDiagnosticManager(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.TextDocument! document, System.Collections.Immutable.ImmutableArray errors) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.TextDocument! document, Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! hotReloadDiagnosticSource) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadErrorService) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry From 6700d1175ba4ceb4031be885408dd8bb99ce659c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 14:51:04 -0700 Subject: [PATCH 123/292] Add main concepts --- .../ITemporaryStorageService.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 694b26ba147b0..d18d46a67b20a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System; +using System.IO; +using System.Runtime.Serialization; using System.Threading; namespace Microsoft.CodeAnalysis.Host; @@ -16,6 +18,40 @@ public interface ITemporaryStorageService : IWorkspaceService internal interface ITemporaryStorageServiceInternal : IWorkspaceService { - ITemporaryStreamStorageInternal CreateTemporaryStreamStorage(); + TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); + Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken); + ITemporaryTextStorageInternal CreateTemporaryTextStorage(); } + +/// +/// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is +/// not disposed, the data should remain in storage and can be readable from any process using the information provided +/// in . Use to write +/// the data to temporary storage and get a handle to it. Use to read the data back in any process. +/// +internal sealed class TemporaryStorageHandle : IDisposable +{ + private IDisposable? _underlyingData; + private TemporaryStorageIdentifier? _identifier; + + public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); + + public void Dispose() + { + var data = Interlocked.Exchange(ref _underlyingData, null); + data?.Dispose(); + _identifier = null; + } +} + +/// +/// Identifier for a stream of data placed in a segment of temporary storage (generally a memory mapped file). Can be +/// used to identify that segment across processes, allowing for efficient sharing of data. +/// +[DataContract] +internal sealed record TemporaryStorageIdentifier( + [property: DataMember(Order = 0)] string Name, + [property: DataMember(Order = 1)] int Offset, + [property: DataMember(Order = 2)] int Length); From dae552bd22f7d03fb72e58c0212dded6b2771746 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 14:55:25 -0700 Subject: [PATCH 124/292] trivial impl --- .../ITemporaryStorageService.cs | 6 +-- .../TrivialTemporaryStorageService.cs | 47 +++++++++---------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index d18d46a67b20a..01fca76eac673 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -31,10 +31,10 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// the data to temporary storage and get a handle to it. Use to read the data back in any process. /// -internal sealed class TemporaryStorageHandle : IDisposable +internal sealed class TemporaryStorageHandle(IDisposable underlyingData, TemporaryStorageIdentifier identifier) : IDisposable { - private IDisposable? _underlyingData; - private TemporaryStorageIdentifier? _identifier; + private IDisposable? _underlyingData = underlyingData; + private TemporaryStorageIdentifier? _identifier = identifier; public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 0f3641b459ca7..8cf30a633ee24 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -4,11 +4,12 @@ using System; using System.IO; -using System.Text; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -16,16 +17,34 @@ internal sealed class TrivialTemporaryStorageService : ITemporaryStorageServiceI { public static readonly TrivialTemporaryStorageService Instance = new(); + private static ConditionalWeakTable s_streamStorage = new(); + private TrivialTemporaryStorageService() { } - public ITemporaryStreamStorageInternal CreateTemporaryStreamStorage() - => new StreamStorage(); - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() => new TextStorage(); + public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + { + var storage = new StreamStorage(); + storage.WriteStream(stream, cancellationToken); + var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), 0, 0); + var handle = new TemporaryStorageHandle(storage, identifier); + + return handle; + } + + public Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + { + Contract.ThrowIfFalse( + s_streamStorage.TryGetValue(storageIdentifier, out var streamStorage), + "StorageIdentifier was not created by this storage service!"); + + return streamStorage.ReadStream(cancellationToken); + } + private sealed class StreamStorage : ITemporaryStreamStorageInternal { private MemoryStream? _stream; @@ -45,11 +64,6 @@ public Stream ReadStream(CancellationToken cancellationToken) return new MemoryStream(stream.GetBuffer(), 0, (int)stream.Length, writable: false); } - public Task ReadStreamAsync(CancellationToken cancellationToken) - { - return Task.FromResult(ReadStream(cancellationToken)); - } - public void WriteStream(Stream stream, CancellationToken cancellationToken) { var newStream = new MemoryStream(); @@ -60,21 +74,6 @@ public void WriteStream(Stream stream, CancellationToken cancellationToken) throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); } } - - public async Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken) - { - var newStream = new MemoryStream(); -#if NETCOREAPP - await stream.CopyToAsync(newStream, cancellationToken).ConfigureAwait(false); -# else - await stream.CopyToAsync(newStream).ConfigureAwait(false); -#endif - var existingValue = Interlocked.CompareExchange(ref _stream, newStream, null); - if (existingValue is not null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - } } private sealed class TextStorage : ITemporaryTextStorageInternal From 7ee761f2f3fae009e0359fd4db575bf4e48c6489 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 14:58:41 -0700 Subject: [PATCH 125/292] Main impl --- .../TemporaryStorageServiceFactory.cs | 28 +++++++++---------- .../ITemporaryStorageService.cs | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index 4fa4e5cf53697..92c68193bb2b7 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -101,14 +101,19 @@ public TemporaryTextStorage AttachTemporaryTextStorage( string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); - ITemporaryStreamStorageInternal ITemporaryStorageServiceInternal.CreateTemporaryStreamStorage() - => CreateTemporaryStreamStorage(); - - internal TemporaryStreamStorage CreateTemporaryStreamStorage() - => new(this); + public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage(this); + storage.WriteStream(stream, cancellationToken); + var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); + return new(storage, identifier); + } - public TemporaryStreamStorage AttachTemporaryStreamStorage(string storageName, long offset, long size) - => new(this, storageName, offset, size); + public Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); + return storage.ReadStream(cancellationToken); + } /// /// Allocate shared storage of a specified size. @@ -310,7 +315,7 @@ private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedM } } - internal sealed class TemporaryStreamStorage : ITemporaryStreamStorageInternal, ITemporaryStorageWithName + internal sealed class TemporaryStreamStorage { private readonly TemporaryStorageService _service; private MemoryMappedInfo? _memoryMappedInfo; @@ -324,9 +329,7 @@ public TemporaryStreamStorage(TemporaryStorageService service, string storageNam _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); } - // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 - // Offset, Size is only used when Name is not null. - public string? Name => _memoryMappedInfo?.Name; + public string Name => _memoryMappedInfo!.Name; public long Offset => _memoryMappedInfo!.Offset; public long Size => _memoryMappedInfo!.Size; @@ -339,9 +342,6 @@ public void Dispose() _memoryMappedInfo = null; } - Stream ITemporaryStreamStorageInternal.ReadStream(CancellationToken cancellationToken) - => ReadStream(cancellationToken); - public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) { if (_memoryMappedInfo == null) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 01fca76eac673..e35b3cfcdd73b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -53,5 +53,5 @@ public void Dispose() [DataContract] internal sealed record TemporaryStorageIdentifier( [property: DataMember(Order = 0)] string Name, - [property: DataMember(Order = 1)] int Offset, - [property: DataMember(Order = 2)] int Length); + [property: DataMember(Order = 1)] long Offset, + [property: DataMember(Order = 2)] long Size); From 8ea948af186ef49ee1d554f7104b73884664ab29 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 15:03:01 -0700 Subject: [PATCH 126/292] Update skeletons --- ...CompilationState.SkeletonReferenceCache.cs | 92 ++++++++++--------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index f0e1ffc395db2..ef93f263e5108 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -218,11 +218,16 @@ public readonly SkeletonReferenceCache Clone() private static SkeletonReferenceSet? CreateSkeletonSet( SolutionServices services, Compilation compilation, CancellationToken cancellationToken) { - var storage = TryCreateMetadataStorage(services, compilation, cancellationToken); - if (storage == null) + var temporaryStorageService = services.GetRequiredService(); + + var handle = TryCreateMetadataStorage(); + if (handle == null) return null; - var metadata = AssemblyMetadata.CreateFromStream(storage.ReadStream(cancellationToken), leaveOpen: false); + // Now read the data back from the stream from the memory mapped file. This will come back as an + // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. + var metadata = AssemblyMetadata.CreateFromStream( + temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); // read in the stream and pass ownership of it to the metadata object. When it is disposed it will dispose // the stream as well. @@ -230,62 +235,63 @@ public readonly SkeletonReferenceCache Clone() metadata, compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - } - - private static ITemporaryStreamStorageInternal? TryCreateMetadataStorage(SolutionServices services, Compilation compilation, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var logger = services.GetService(); - try + TemporaryStorageHandle? TryCreateMetadataStorage() { - logger?.Log($"Beginning to create a skeleton assembly for {compilation.AssemblyName}..."); + cancellationToken.ThrowIfCancellationRequested(); - using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) - { - using var stream = SerializableBytes.CreateWritableStream(); + var logger = services.GetService(); - var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); + try + { + logger?.Log($"Beginning to create a skeleton assembly for {compilation.AssemblyName}..."); - if (emitResult.Success) + using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) { - logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + using var stream = SerializableBytes.CreateWritableStream(); - var temporaryStorageService = services.GetRequiredService(); - var storage = temporaryStorageService.CreateTemporaryStreamStorage(); + // First, emit the data to an in-memory stream. + var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); - stream.Position = 0; - storage.WriteStream(stream, cancellationToken); + if (emitResult.Success) + { + logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); - return storage; - } + // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the + // assembly-metadata point directly to that pointer in memory, instead of it having to make its + // own copy it needs to own the lifetime of. + stream.Position = 0; + var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); - if (logger != null) - { - logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + return handle; + } - foreach (var diagnostic in emitResult.Diagnostics) + if (logger != null) { - logger.Log(" " + diagnostic.GetMessage()); + logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + + foreach (var diagnostic in emitResult.Diagnostics) + { + logger.Log(" " + diagnostic.GetMessage()); + } } - } - // log emit failures so that we can improve most common cases - Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => - { - // log errors in the format of - // CS0001:1;CS002:10;... - var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); - m["Errors"] = string.Join(";", groups); - })); + // log emit failures so that we can improve most common cases + Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => + { + // log errors in the format of + // CS0001:1;CS002:10;... + var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); + m["Errors"] = string.Join(";", groups); + })); - return null; + return null; + } + } + finally + { + logger?.Log($"Done trying to create a skeleton assembly for {compilation.AssemblyName}"); } - } - finally - { - logger?.Log($"Done trying to create a skeleton assembly for {compilation.AssemblyName}"); } } } From 330f53c587c47fa5146cdf59ab2d955ce8392555 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 15:10:50 -0700 Subject: [PATCH 127/292] in progress --- .../Core/Portable/Serialization/SerializerService_Reference.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f39c0d444450b..d0a694c0284ae 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -451,7 +451,7 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial return metadata; } - private (ITemporaryStreamStorageInternal storage, long length) GetTemporaryStorage( + private (TemporaryStorageHandle handle, long length) GetTemporaryStorage( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); From 02c8d56098e4f2e297595374cc0a822e40ea9379 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 15:14:57 -0700 Subject: [PATCH 128/292] in progress --- .../SerializerService_Reference.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index d0a694c0284ae..a0e8c3fbbf584 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -23,8 +23,6 @@ internal partial class SerializerService { private const int MetadataFailed = int.MaxValue; - private static readonly ConditionalWeakTable s_lifetimeMap = new(); - public static Checksum CreateChecksum(MetadataReference reference, CancellationToken cancellationToken) { if (reference is PortableExecutableReference portable) @@ -440,13 +438,16 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial Contract.ThrowIfFalse(SerializationKinds.Bits == kind); var array = reader.ReadByteArray(); - var pinnedObject = new PinnedObject(array); - var metadata = ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), array.Length); + // Pin the array so that the module metadata can treat it as a segment of unmanaged memory. + var pinnedObject = new PinnedObject(array); - // make sure we keep storageStream alive while Metadata is alive - // we use conditional weak table since we can't control metadata liftetime - s_lifetimeMap.Add(metadata, pinnedObject); + // The pinned object will be kept alive as long as the metadata is alive due to us passing in + // pinnedObject.Dispose as the onDispose callback. So we do not have to worry about the GC preemptively + // cleaning things up while the metadata is alive. Nor do we have to worry about the pinned object being + // leaked, locking the bytes in place. + var metadata = ModuleMetadata.CreateFromMetadata( + pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); return metadata; } @@ -487,17 +488,15 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial } } - private static void GetMetadata(Stream stream, long length, out ModuleMetadata metadata, out object? lifeTimeObject) + private static ModuleMetadata GetMetadata(Stream stream, long length) { if (stream is UnmanagedMemoryStream unmanagedStream) { // For an unmanaged memory stream, ModuleMetadata can take ownership directly. unsafe { - metadata = ModuleMetadata.CreateFromMetadata( + return ModuleMetadata.CreateFromMetadata( (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - lifeTimeObject = null; - return; } } @@ -515,8 +514,7 @@ private static void GetMetadata(Stream stream, long length, out ModuleMetadata m pinnedObject = new PinnedObject(array); } - metadata = ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), (int)length); - lifeTimeObject = pinnedObject; + return ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), (int)length, pinnedObject.Dispose); } private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) From 6533c3411f397045697ed2144cc55704aaab1439 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 15:18:38 -0700 Subject: [PATCH 129/292] Simplify lifetime management of ModuleMetadata instances created from pointers to managed memory --- .../SerializerService_Reference.cs | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f39c0d444450b..bcfbc4d34d1e7 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -23,8 +23,6 @@ internal partial class SerializerService { private const int MetadataFailed = int.MaxValue; - private static readonly ConditionalWeakTable s_lifetimeMap = new(); - public static Checksum CreateChecksum(MetadataReference reference, CancellationToken cancellationToken) { if (reference is PortableExecutableReference portable) @@ -425,12 +423,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var storageStream = storage.ReadStream(cancellationToken); Contract.ThrowIfFalse(length == storageStream.Length); - GetMetadata(storageStream, length, out var metadata, out var lifeTimeObject); - - // make sure we keep storageStream alive while Metadata is alive - // we use conditional weak table since we can't control metadata liftetime - if (lifeTimeObject != null) - s_lifetimeMap.Add(metadata, lifeTimeObject); + var metadata = GetMetadata(storageStream, length); return (metadata, storage); } @@ -442,13 +435,10 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial var array = reader.ReadByteArray(); var pinnedObject = new PinnedObject(array); - var metadata = ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), array.Length); - - // make sure we keep storageStream alive while Metadata is alive - // we use conditional weak table since we can't control metadata liftetime - s_lifetimeMap.Add(metadata, pinnedObject); - - return metadata; + // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in + // as the onDispose callback of the metadata. + return ModuleMetadata.CreateFromMetadata( + pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); } private (ITemporaryStreamStorageInternal storage, long length) GetTemporaryStorage( @@ -487,17 +477,17 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial } } - private static void GetMetadata(Stream stream, long length, out ModuleMetadata metadata, out object? lifeTimeObject) + private static ModuleMetadata GetMetadata(Stream stream, long length) { if (stream is UnmanagedMemoryStream unmanagedStream) { - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. unsafe { - metadata = ModuleMetadata.CreateFromMetadata( + return ModuleMetadata.CreateFromMetadata( (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - lifeTimeObject = null; - return; } } @@ -515,8 +505,10 @@ private static void GetMetadata(Stream stream, long length, out ModuleMetadata m pinnedObject = new PinnedObject(array); } - metadata = ModuleMetadata.CreateFromMetadata(pinnedObject.GetPointer(), (int)length); - lifeTimeObject = pinnedObject; + // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in + // as the onDispose callback of the metadata. + return ModuleMetadata.CreateFromMetadata( + pinnedObject.GetPointer(), (int)length, pinnedObject.Dispose); } private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) From 8bbd01c3da50c90f7d9b908ea421b820a2306448 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 17:32:57 -0700 Subject: [PATCH 130/292] in progress --- .../Host/TemporaryStorage/ITemporaryStorage.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index ef647f1af7b36..0e51a34da6993 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -41,8 +41,8 @@ internal interface ITemporaryTextStorageInternal : IDisposable Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); } -internal interface ITemporaryStreamStorageInternal : IDisposable -{ - Stream ReadStream(CancellationToken cancellationToken = default); - void WriteStream(Stream stream, CancellationToken cancellationToken = default); -} +//internal interface ITemporaryStreamStorageInternal : IDisposable +//{ +// Stream ReadStream(CancellationToken cancellationToken = default); +// void WriteStream(Stream stream, CancellationToken cancellationToken = default); +//} From 9d2b53738aa59b5de5c49320113d27813c99614b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 17:48:06 -0700 Subject: [PATCH 131/292] Remove solution replicatoin context --- .../Portable/Remote/ISerializerService.cs | 2 +- .../Serialization/SerializableSourceText.cs | 4 +-- .../Serialization/SerializerService.cs | 8 ++--- .../Serialization/SerializerService_Asset.cs | 8 ++--- .../SerializerService_Reference.cs | 8 ++--- .../SolutionReplicationContext.cs | 31 ------------------- .../Workspace/Solution/Checksum_Factory.cs | 14 +++------ .../Fakes/SimpleAssetSource.cs | 3 +- .../Remote/TestSerializerService.cs | 4 +-- .../Remote/Core/RemoteHostAssetWriter.cs | 2 +- .../Remote/Core/SolutionAssetStorage.Scope.cs | 6 ---- .../Remote/Core/SolutionAssetStorage.cs | 2 -- 12 files changed, 22 insertions(+), 70 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs diff --git a/src/Workspaces/Core/Portable/Remote/ISerializerService.cs b/src/Workspaces/Core/Portable/Remote/ISerializerService.cs index f6f74f5bae648..f56f5f0a89417 100644 --- a/src/Workspaces/Core/Portable/Remote/ISerializerService.cs +++ b/src/Workspaces/Core/Portable/Remote/ISerializerService.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Serialization; internal interface ISerializerService : IWorkspaceService { - void Serialize(object value, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken); + void Serialize(object value, ObjectWriter writer, CancellationToken cancellationToken); void SerializeParseOptions(ParseOptions options, ObjectWriter writer); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index ad9ded5d6bd10..86bec3e6decad 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -139,13 +139,11 @@ public static ValueTask FromTextDocumentStateAsync( } } - public void Serialize(ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public void Serialize(ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (_storage is not null) { - context.AddResource(_storage); - writer.WriteInt32((int)_storage.ChecksumAlgorithm); writer.WriteEncoding(_storage.Encoding); writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_storage.ContentHash)!); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index e263fdad2c23b..02868db1077ea 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -75,7 +75,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken case WellKnownSynchronizationKind.ParseOptions: case WellKnownSynchronizationKind.ProjectReference: case WellKnownSynchronizationKind.SourceGeneratedDocumentIdentity: - return Checksum.Create(value, this); + return Checksum.Create(value, this, cancellationToken); case WellKnownSynchronizationKind.MetadataReference: return CreateChecksum((MetadataReference)value, cancellationToken); @@ -94,7 +94,7 @@ public Checksum CreateChecksum(object value, CancellationToken cancellationToken } } - public void Serialize(object value, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public void Serialize(object value, ObjectWriter writer, CancellationToken cancellationToken) { var kind = value.GetWellKnownSynchronizationKind(); @@ -134,7 +134,7 @@ public void Serialize(object value, ObjectWriter writer, SolutionReplicationCont return; case WellKnownSynchronizationKind.MetadataReference: - SerializeMetadataReference((MetadataReference)value, writer, context, cancellationToken); + SerializeMetadataReference((MetadataReference)value, writer, cancellationToken); return; case WellKnownSynchronizationKind.AnalyzerReference: @@ -142,7 +142,7 @@ public void Serialize(object value, ObjectWriter writer, SolutionReplicationCont return; case WellKnownSynchronizationKind.SerializableSourceText: - SerializeSourceText((SerializableSourceText)value, writer, context, cancellationToken); + SerializeSourceText((SerializableSourceText)value, writer, cancellationToken); return; case WellKnownSynchronizationKind.SolutionCompilationState: diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs index c3842c9ff3870..d7bbeba322659 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Asset.cs @@ -17,9 +17,9 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal partial class SerializerService { - private static void SerializeSourceText(SerializableSourceText text, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + private static void SerializeSourceText(SerializableSourceText text, ObjectWriter writer, CancellationToken cancellationToken) { - text.Serialize(writer, context, cancellationToken); + text.Serialize(writer, cancellationToken); } private void SerializeCompilationOptions(CompilationOptions options, ObjectWriter writer, CancellationToken cancellationToken) @@ -86,10 +86,10 @@ private static ProjectReference DeserializeProjectReference(ObjectReader reader, return new ProjectReference(projectId, aliases.ToImmutableArrayOrEmpty(), embedInteropTypes); } - private void SerializeMetadataReference(MetadataReference reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + private void SerializeMetadataReference(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - WriteMetadataReferenceTo(reference, writer, context, cancellationToken); + WriteMetadataReferenceTo(reference, writer, cancellationToken); } private MetadataReference DeserializeMetadataReference(ObjectReader reader, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index bcfbc4d34d1e7..c5e19134e2309 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -60,13 +60,13 @@ public static Checksum CreateChecksum(AnalyzerReference reference, CancellationT return Checksum.Create(stream); } - public virtual void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public virtual void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { if (reference is PortableExecutableReference portable) { if (portable is ISupportTemporaryStorage supportTemporaryStorage) { - if (TryWritePortableExecutableReferenceBackedByTemporaryStorageTo(supportTemporaryStorage, writer, context, cancellationToken)) + if (TryWritePortableExecutableReferenceBackedByTemporaryStorageTo(supportTemporaryStorage, writer, cancellationToken)) { return; } @@ -311,7 +311,7 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio } private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( - ISupportTemporaryStorage reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + ISupportTemporaryStorage reference, ObjectWriter writer, CancellationToken cancellationToken) { var storages = reference.GetStorages(); if (storages == null) @@ -329,8 +329,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return false; } - context.AddResource(storage); - pooled.Object.Add((storage2.Name, storage2.Offset, storage2.Size)); } diff --git a/src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs b/src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs deleted file mode 100644 index e1cb61be7218f..0000000000000 --- a/src/Workspaces/Core/Portable/Serialization/SolutionReplicationContext.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Serialization; - -internal readonly struct SolutionReplicationContext : IDisposable -{ - private static readonly ObjectPool> s_pool = new(() => []); - - private readonly ConcurrentSet _resources; - - public SolutionReplicationContext() - => _resources = s_pool.Allocate(); - - public void AddResource(IDisposable resource) - => _resources.Add(resource); - - public void Dispose() - { - // TODO: https://github.com/dotnet/roslyn/issues/49973 - // Currently we don't dispose resources, only keep them alive. - // Shouldn't we dispose them? - // _resources.All(resource => resource.Dispose()); - s_pool.ClearAndFree(_resources); - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs index 95519835568d7..ead02e2f244d2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Checksum_Factory.cs @@ -105,16 +105,12 @@ public static Checksum Create(ImmutableArray bytes) writer.WriteByte(b); }); - public static Checksum Create(T value, ISerializerService serializer) - { - using var context = new SolutionReplicationContext(); - - return Create( - (value, serializer, context), + public static Checksum Create(T value, ISerializerService serializer, CancellationToken cancellationToken) + => Create( + (value, serializer, cancellationToken), static (tuple, writer) => { - var (value, serializer, context) = tuple; - serializer.Serialize(value!, writer, context, CancellationToken.None); + var (value, serializer, cancellationToken) = tuple; + serializer.Serialize(value!, writer, cancellationToken); }); - } } diff --git a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs index ab8478a29d58e..e9e24ba94cab1 100644 --- a/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs +++ b/src/Workspaces/CoreTestUtilities/Fakes/SimpleAssetSource.cs @@ -26,11 +26,10 @@ public ValueTask GetAssetsAsync( Contract.ThrowIfFalse(map.TryGetValue(checksum, out var data)); using var stream = new MemoryStream(); - using var context = new SolutionReplicationContext(); using (var writer = new ObjectWriter(stream, leaveOpen: true)) { - serializerService.Serialize(data, writer, context, cancellationToken); + serializerService.Serialize(data, writer, cancellationToken); } stream.Position = 0; diff --git a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs index 8c42582dd37cc..bf86ae8934251 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs @@ -41,7 +41,7 @@ public TestSerializerService(ConcurrentDictionary _sharedTestGeneratorReferences = sharedTestGeneratorReferences; } - public override void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, SolutionReplicationContext context, CancellationToken cancellationToken) + public override void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { var wellKnownReferenceName = s_wellKnownReferenceNames.GetValueOrDefault(reference, null); if (wellKnownReferenceName is not null) @@ -52,7 +52,7 @@ public override void WriteMetadataReferenceTo(MetadataReference reference, Objec else { writer.WriteBoolean(false); - base.WriteMetadataReferenceTo(reference, writer, context, cancellationToken); + base.WriteMetadataReferenceTo(reference, writer, cancellationToken); } } diff --git a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs index ca05cccdec70f..27cdfea93180d 100644 --- a/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs +++ b/src/Workspaces/Remote/Core/RemoteHostAssetWriter.cs @@ -219,7 +219,7 @@ private void WriteAssetToTempStream( objectWriter.WriteByte((byte)asset.GetWellKnownSynchronizationKind()); // Now serialize out the asset itself. - _serializer.Serialize(asset, objectWriter, _scope.ReplicationContext, cancellationToken); + _serializer.Serialize(asset, objectWriter, cancellationToken); } private void WriteSentinelByteToPipeWriter() diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index acc4b58edc971..845f823512bf4 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -25,12 +25,6 @@ internal sealed partial class Scope( public readonly ProjectCone? ProjectCone = projectCone; public readonly SolutionCompilationState CompilationState = compilationState; - /// - /// Will be disposed from when the last ref-count to this scope goes - /// away. - /// - public readonly SolutionReplicationContext ReplicationContext = new(); - /// /// Only safe to read write while is held. /// diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs index 8e87958c46eb3..d5f80f5030c92 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs @@ -111,8 +111,6 @@ private void DecreaseScopeRefCount(Scope scope) // Last ref went away, update our maps while under the lock, then cleanup its context data outside of the lock. _checksumToScope.Remove(solutionChecksum); } - - scope.ReplicationContext.Dispose(); } internal TestAccessor GetTestAccessor() From ebd529ee1f8c697ebc9aa0f59f0e6a7f68d48be5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 17:55:26 -0700 Subject: [PATCH 132/292] Fix --- .../MetadataReferences/VisualStudioMetadataReferenceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 8fe74c01b94e9..a679387a69a87 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -214,7 +214,7 @@ void GetStorageInfoFromTemporaryStorage( storage = _temporaryStorageService.CreateTemporaryStreamStorage(); copyStream.Position = 0; - storage.WriteStream(copyStream); + storage.WriteStream(copyStream, CancellationToken.None); } // get stream that owns the underlying unmanaged memory. From d5328b5a1a33008e00d15ee40ac8da5a4a831ad3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 17:56:29 -0700 Subject: [PATCH 133/292] Fix --- .../MetadataReferences/VisualStudioMetadataReferenceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 8fe74c01b94e9..a679387a69a87 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -214,7 +214,7 @@ void GetStorageInfoFromTemporaryStorage( storage = _temporaryStorageService.CreateTemporaryStreamStorage(); copyStream.Position = 0; - storage.WriteStream(copyStream); + storage.WriteStream(copyStream, CancellationToken.None); } // get stream that owns the underlying unmanaged memory. From 5a6b83554f1d977bb7e687e8b6127b210337f3b5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 18:11:00 -0700 Subject: [PATCH 134/292] In progress --- .../Serialization/ISupportTemporaryStorage.cs | 2 +- .../SerializerService_Reference.cs | 23 ++------- .../ITemporaryStreamStorageExtensions.cs | 47 ------------------- .../TrivialTemporaryStorageService.cs | 12 ++--- .../ProjectSystemProjectOptionsProcessor.cs | 46 +++++++++++++++--- 5 files changed, 50 insertions(+), 80 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index e17bcb1a26d4f..d0d18406c8333 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -13,5 +13,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? GetStorages(); + IReadOnlyList? GetStorageIdentifiers(); } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 9439bccd2e83d..bf66d91062e9f 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -313,31 +313,16 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( ISupportTemporaryStorage reference, ObjectWriter writer, CancellationToken cancellationToken) { - var storages = reference.GetStorages(); - if (storages == null) - { + var storageIdentifiers = reference.GetStorageIdentifiers(); + if (storageIdentifiers == null) return false; - } - - // Not clear if name should be allowed to be null here (https://github.com/dotnet/roslyn/issues/43037) - using var pooled = Creator.CreateList<(string? name, long offset, long size)>(); - - foreach (var storage in storages) - { - if (storage is not ITemporaryStorageWithName storage2) - { - return false; - } - - pooled.Object.Add((storage2.Name, storage2.Offset, storage2.Size)); - } WritePortableExecutableReferenceHeaderTo((PortableExecutableReference)reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); writer.WriteInt32((int)MetadataImageKind.Assembly); - writer.WriteInt32(pooled.Object.Count); + writer.WriteInt32(storageIdentifiers.Count); - foreach (var (name, offset, size) in pooled.Object) + foreach (var (name, offset, size) in storageIdentifiers) { writer.WriteInt32((int)MetadataImageKind.Module); writer.WriteString(name); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs deleted file mode 100644 index af0d841bea463..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStreamStorageExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; - -namespace Microsoft.CodeAnalysis.Host; - -internal static class ITemporaryStreamStorageExtensions -{ - public static void WriteAllLines(this ITemporaryStreamStorageInternal storage, ImmutableArray values) - { - using var stream = SerializableBytes.CreateWritableStream(); - using var writer = new StreamWriter(stream); - - foreach (var value in values) - { - writer.WriteLine(value); - } - - writer.Flush(); - stream.Position = 0; - - storage.WriteStream(stream); - } - - public static ImmutableArray ReadLines(this ITemporaryStreamStorageInternal storage) - { - return EnumerateLines(storage).ToImmutableArray(); - } - - private static IEnumerable EnumerateLines(ITemporaryStreamStorageInternal storage) - { - using var stream = storage.ReadStream(); - using var reader = new StreamReader(stream); - - string line; - while ((line = reader.ReadLine()) != null) - { - yield return line; - } - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 8cf30a633ee24..de638eb1272de 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -17,7 +17,7 @@ internal sealed class TrivialTemporaryStorageService : ITemporaryStorageServiceI { public static readonly TrivialTemporaryStorageService Instance = new(); - private static ConditionalWeakTable s_streamStorage = new(); + private static readonly ConditionalWeakTable s_streamStorage = new(); private TrivialTemporaryStorageService() { @@ -29,7 +29,7 @@ public ITemporaryTextStorageInternal CreateTemporaryTextStorage() public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { var storage = new StreamStorage(); - storage.WriteStream(stream, cancellationToken); + storage.WriteStream(stream); var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), 0, 0); var handle = new TemporaryStorageHandle(storage, identifier); @@ -42,10 +42,10 @@ public Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storage s_streamStorage.TryGetValue(storageIdentifier, out var streamStorage), "StorageIdentifier was not created by this storage service!"); - return streamStorage.ReadStream(cancellationToken); + return streamStorage.ReadStream(); } - private sealed class StreamStorage : ITemporaryStreamStorageInternal + private sealed class StreamStorage : IDisposable { private MemoryStream? _stream; @@ -55,7 +55,7 @@ public void Dispose() _stream = null; } - public Stream ReadStream(CancellationToken cancellationToken) + public Stream ReadStream() { var stream = _stream ?? throw new InvalidOperationException(); @@ -64,7 +64,7 @@ public Stream ReadStream(CancellationToken cancellationToken) return new MemoryStream(stream.GetBuffer(), 0, (int)stream.Length, writable: false); } - public void WriteStream(Stream stream, CancellationToken cancellationToken) + public void WriteStream(Stream stream) { var newStream = new MemoryStream(); stream.CopyTo(newStream); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 11be0ef802128..04fcee98897cd 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -3,8 +3,11 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; +using System.Threading; using Microsoft.CodeAnalysis.Host; using Roslyn.Utilities; @@ -36,7 +39,7 @@ internal class ProjectSystemProjectOptionsProcessor : IDisposable /// (especially in cases with many references). /// /// Note: this will be null in the case that the command line is an empty array. - private ITemporaryStreamStorageInternal? _commandLineStorage; + private TemporaryStorageHandle? _commandLineStorageHandle; private CommandLineArguments _commandLineArgumentsForCommandLine; private string? _explicitRuleSetFilePath; @@ -71,12 +74,20 @@ private bool ReparseCommandLineIfChanged_NoLock(ImmutableArray arguments // Dispose the existing stored command-line and then persist the new one so we can // recover it later. Only bother persisting things if we have a non-empty string. - _commandLineStorage?.Dispose(); - _commandLineStorage = null; + _commandLineStorageHandle?.Dispose(); + _commandLineStorageHandle = null; if (!arguments.IsEmpty) { - _commandLineStorage = _temporaryStorageService.CreateTemporaryStreamStorage(); - _commandLineStorage.WriteAllLines(arguments); + using var stream = SerializableBytes.CreateWritableStream(); + using var writer = new StreamWriter(stream); + + foreach (var value in arguments) + writer.WriteLine(value); + + writer.Flush(); + stream.Position = 0; + + _commandLineStorageHandle = _temporaryStorageService.WriteToTemporaryStorage(stream, CancellationToken.None); } ReparseCommandLine_NoLock(arguments); @@ -237,12 +248,33 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) // effective values was potentially done by the act of parsing the command line. Even though the command line didn't change textually, // the effective result did. Then we call UpdateProjectOptions_NoLock to reapply any values; that will also re-acquire the new ruleset // includes in the IDE so we can be watching for changes again. - var commandLine = _commandLineStorage == null ? ImmutableArray.Empty : _commandLineStorage.ReadLines(); + var commandLine = _commandLineStorageHandle == null + ? ImmutableArray.Empty + : ReadLines(_temporaryStorageService, _commandLineStorageHandle.Identifier); DisposeOfRuleSetFile_NoLock(); ReparseCommandLine_NoLock(commandLine); UpdateProjectOptions_NoLock(); } + + static ImmutableArray ReadLines( + ITemporaryStorageServiceInternal temporaryStorageService, + TemporaryStorageIdentifier storageIdentifier) + { + return EnumerateLines(temporaryStorageService, storageIdentifier).ToImmutableArray(); + } + + static IEnumerable EnumerateLines( + ITemporaryStorageServiceInternal temporaryStorageService, + TemporaryStorageIdentifier storageIdentifier) + { + using var stream = temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); + using var reader = new StreamReader(stream); + + string? line; + while ((line = reader.ReadLine()) != null) + yield return line; + } } /// @@ -276,7 +308,7 @@ public void Dispose() lock (_gate) { DisposeOfRuleSetFile_NoLock(); - _commandLineStorage?.Dispose(); + _commandLineStorageHandle?.Dispose(); } } } From 287f83671948d26561bf617b137369c494c70e4b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 18:23:31 -0700 Subject: [PATCH 135/292] In progress --- .../SerializerService_Reference.cs | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index bf66d91062e9f..162f070175bf3 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -333,7 +333,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return true; } - private (Metadata metadata, ImmutableArray storages)? TryReadMetadataFrom( + private (Metadata metadata, ImmutableArray storageIdentifiers)? TryReadMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { var imageKind = reader.ReadInt32(); @@ -361,19 +361,19 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT #pragma warning restore CA2016 } - return (AssemblyMetadata.Create(pooledMetadata.Object), storages: default); + return (AssemblyMetadata.Create(pooledMetadata.Object), storageIdentifiers: default); } Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); #pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storages: default); + return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: default); #pragma warning restore CA2016 } if (metadataKind == MetadataImageKind.Assembly) { using var pooledMetadata = Creator.CreateList(); - using var pooledStorage = Creator.CreateList(); + using var pooledStorageIdentifiers = Creator.CreateList(); var count = reader.ReadInt32(); for (var i = 0; i < count; i++) @@ -381,13 +381,13 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT metadataKind = (MetadataImageKind)reader.ReadInt32(); Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - var (metadata, storage) = ReadModuleMetadataFrom(reader, kind, cancellationToken); + var (metadata, storageIdentifier) = ReadModuleMetadataFrom(reader, kind, cancellationToken); pooledMetadata.Object.Add(metadata); - pooledStorage.Object.Add(storage); + pooledStorageIdentifiers.Object.Add(storageIdentifier); } - return (AssemblyMetadata.Create(pooledMetadata.Object), pooledStorage.Object.ToImmutableArrayOrEmpty()); + return (AssemblyMetadata.Create(pooledMetadata.Object), pooledStorageIdentifiers.Object.ToImmutableArrayOrEmpty()); } Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); @@ -396,7 +396,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return (moduleInfo.metadata, ImmutableArray.Create(moduleInfo.storage)); } - private (ModuleMetadata metadata, ITemporaryStreamStorageInternal storage) ReadModuleMetadataFrom( + private (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifeir) ReadModuleMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -426,39 +426,33 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); } - private (TemporaryStorageHandle handle, long length) GetTemporaryStorage( + private (TemporaryStorageIdentifier handle, long length) GetTemporaryStorageIdentifier( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); if (kind == SerializationKinds.Bits) { - var storage = _storageService.CreateTemporaryStreamStorage(); + // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the + // server side so that we can refer to this data uniformly. using var stream = SerializableBytes.CreateWritableStream(); - CopyByteArrayToStream(reader, stream, cancellationToken); var length = stream.Length; stream.Position = 0; - storage.WriteStream(stream, cancellationToken); + var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); - return (storage, length); + return (storageHandle.Identifier, length); } else { - var service2 = (TemporaryStorageService)_storageService; - + // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it + // will not be released by the host. var name = reader.ReadRequiredString(); var offset = reader.ReadInt64(); var size = reader.ReadInt64(); - -#pragma warning disable CA1416 // Validate platform compatibility - var storage = service2.AttachTemporaryStreamStorage(name, offset, size); -#pragma warning restore CA1416 // Validate platform compatibility - var length = size; - - return (storage, length); + return (new TemporaryStorageIdentifier(name, offset, size), size); } } From b28dcb8238a8a592351d0947d926e06e8a9c08c3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 18:26:45 -0700 Subject: [PATCH 136/292] In progress --- .../Serialization/SerializerService_Reference.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 162f070175bf3..9e15099f1640c 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -393,22 +393,26 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); var moduleInfo = ReadModuleMetadataFrom(reader, kind, cancellationToken); - return (moduleInfo.metadata, ImmutableArray.Create(moduleInfo.storage)); + return (moduleInfo.metadata, [moduleInfo.storageIdentifier]); } - private (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifeir) ReadModuleMetadataFrom( + private (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var (storage, length) = GetTemporaryStorage(reader, kind, cancellationToken); + // Get the storage identifier for the module metadata. + var (storageIdentifier, length) = GetTemporaryStorageIdentifier(reader, kind, cancellationToken); - var storageStream = storage.ReadStream(cancellationToken); + // Now read in the module data using that identifier. This will either be reading from the host's memory if + // they passed us the information about that memory segment. Or it will be reading from our own memory if they + // sent us the full contents. + var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); Contract.ThrowIfFalse(length == storageStream.Length); var metadata = GetMetadata(storageStream, length); - return (metadata, storage); + return (metadata, storageIdentifier); } private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) From e3315975eefcb1d38ac09e24b5c4dac397550669 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 19:12:53 -0700 Subject: [PATCH 137/292] In progress --- .../VisualStudioMetadataReferenceManager.cs | 28 +++++++++---------- .../TemporaryStorageServiceFactory.cs | 7 +++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index a679387a69a87..866a914ab79e6 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -43,7 +43,7 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS /// the remote process, and it can map that same memory in directly, instead of needing the host to send the /// entire contents of the assembly over the channel to the OOP process. /// - private static readonly ConditionalWeakTable> s_metadataToStorages = new(); + private static readonly ConditionalWeakTable> s_metadataToStorageIdentifiers = new(); private readonly MetadataCache _metadataCache = new(); private readonly ImmutableArray _runtimeDirectories; @@ -86,12 +86,12 @@ public void Dispose() } } - public IReadOnlyList? GetStorages(string fullPath, DateTime snapshotTimestamp) + public IReadOnlyList? GetStorageIdentifiers(string fullPath, DateTime snapshotTimestamp) { var key = new FileKey(fullPath, snapshotTimestamp); // check existing metadata if (_metadataCache.TryGetMetadata(key, out var source) && - s_metadataToStorages.TryGetValue(source, out var storages)) + s_metadataToStorageIdentifiers.TryGetValue(source, out var storages)) { return storages; } @@ -154,19 +154,18 @@ AssemblyMetadata GetMetadataWorker() else { // use temporary storage - using var _ = ArrayBuilder.GetInstance(out var storages); + using var _ = ArrayBuilder.GetInstance(out var storageIdentifiers); var newMetadata = CreateAssemblyMetadata(key, key => { // // - GetMetadataFromTemporaryStorage(key, out var storage, out var metadata); - storages.Add(storage); + GetMetadataFromTemporaryStorage(key, out var storageIdentifier, out var metadata); + storageIdentifiers.Add(storageIdentifier); return metadata; }); - var storagesArray = storages.ToImmutable(); - s_metadataToStorages.Add(newMetadata, storagesArray); + s_metadataToStorageIdentifiers.Add(newMetadata, storageIdentifiers.ToImmutable()); return newMetadata; } @@ -174,9 +173,9 @@ AssemblyMetadata GetMetadataWorker() } private void GetMetadataFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageService.TemporaryStreamStorage storage, out ModuleMetadata metadata) + FileKey moduleFileKey, out TemporaryStorageIdentifier storageIdentifier, out ModuleMetadata metadata) { - GetStorageInfoFromTemporaryStorage(moduleFileKey, out storage, out var stream); + GetStorageInfoFromTemporaryStorage(moduleFileKey, out storageIdentifier, out var stream); unsafe { @@ -187,7 +186,7 @@ private void GetMetadataFromTemporaryStorage( return; void GetStorageInfoFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageService.TemporaryStreamStorage storage, out UnmanagedMemoryStream stream) + FileKey moduleFileKey, out TemporaryStorageIdentifier storageIdentifier, out UnmanagedMemoryStream stream) { int size; using (var copyStream = SerializableBytes.CreateWritableStream()) @@ -211,14 +210,13 @@ void GetStorageInfoFromTemporaryStorage( } // copy over the data to temp storage and let pooled stream go - storage = _temporaryStorageService.CreateTemporaryStreamStorage(); - copyStream.Position = 0; - storage.WriteStream(copyStream, CancellationToken.None); + var handle = _temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); + storageIdentifier = handle.Identifier; } // get stream that owns the underlying unmanaged memory. - stream = storage.ReadStream(CancellationToken.None); + stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); // stream size must be same as what metadata reader said the size should be. Contract.ThrowIfFalse(stream.Length == size); diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index 92c68193bb2b7..365a2870ab1cb 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -109,7 +109,10 @@ public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, Cancellatio return new(storage, identifier); } - public Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + Stream ITemporaryStorageServiceInternal.ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + => ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + + public UnmanagedMemoryStream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) { var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); return storage.ReadStream(cancellationToken); @@ -315,7 +318,7 @@ private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedM } } - internal sealed class TemporaryStreamStorage + internal sealed class TemporaryStreamStorage : IDisposable { private readonly TemporaryStorageService _service; private MemoryMappedInfo? _memoryMappedInfo; From b3cd483053c94fb7132eb1d4291b9e94f29d719a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 19:23:17 -0700 Subject: [PATCH 138/292] In progress --- .../VisualStudioMetadataReference.Snapshot.cs | 4 +-- .../VisualStudioMetadataReferenceManager.cs | 3 +-- .../SerializerService_Reference.cs | 27 ++++++++++--------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 8b8d1b5f0cc5f..98423db28fab4 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList GetStorages() - => _provider.GetStorages(this.FilePath, _timestamp.Value); + public IReadOnlyList GetStorageIdentifiers() + => _provider.GetStorageIdentifiers(this.FilePath, _timestamp.Value); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 866a914ab79e6..280a5ec1c30cf 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -154,7 +154,7 @@ AssemblyMetadata GetMetadataWorker() else { // use temporary storage - using var _ = ArrayBuilder.GetInstance(out var storageIdentifiers); + using var _ = ArrayBuilder.GetInstance(out var storageIdentifiers); var newMetadata = CreateAssemblyMetadata(key, key => { // @@ -164,7 +164,6 @@ AssemblyMetadata GetMetadataWorker() return metadata; }); - s_metadataToStorageIdentifiers.Add(newMetadata, storageIdentifiers.ToImmutable()); return newMetadata; diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 9e15099f1640c..eecf1079e1e95 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -233,8 +233,7 @@ private PortableExecutableReference ReadPortableExecutableReferenceFrom(ObjectRe var filePath = reader.ReadString(); - var tuple = TryReadMetadataFrom(reader, kind, cancellationToken); - if (tuple == null) + if (TryReadMetadataFrom(reader, kind, cancellationToken) is not (var metadata, var storageIdentifiers)) { // TODO: deal with xml document provider properly // should we shadow copy xml doc comment? @@ -254,7 +253,7 @@ private PortableExecutableReference ReadPortableExecutableReferenceFrom(ObjectRe _documentationService.GetDocumentationProvider(filePath) : XmlDocumentationProvider.Default; return new SerializedMetadataReference( - properties, filePath, tuple.Value.metadata, tuple.Value.storages, documentProvider); + properties, filePath, metadata, storageIdentifiers, documentProvider); } private static void WriteTo(MetadataReferenceProperties properties, ObjectWriter writer, CancellationToken cancellationToken) @@ -361,12 +360,12 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT #pragma warning restore CA2016 } - return (AssemblyMetadata.Create(pooledMetadata.Object), storageIdentifiers: default); + return (AssemblyMetadata.Create(pooledMetadata.Object), storageIdentifiers: []); } Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); #pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: default); + return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: []); #pragma warning restore CA2016 } @@ -604,16 +603,20 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private sealed class SerializedMetadataReference : PortableExecutableReference, ISupportTemporaryStorage { private readonly Metadata _metadata; - private readonly ImmutableArray _storagesOpt; + private readonly ImmutableArray _storageIdentifiers; private readonly DocumentationProvider _provider; public SerializedMetadataReference( - MetadataReferenceProperties properties, string? fullPath, - Metadata metadata, ImmutableArray storagesOpt, DocumentationProvider initialDocumentation) + MetadataReferenceProperties properties, + string? fullPath, + Metadata metadata, + ImmutableArray storagesIdentifiers, + DocumentationProvider initialDocumentation) : base(properties, fullPath, initialDocumentation) { + Contract.ThrowIfTrue(storagesIdentifiers.IsDefault); _metadata = metadata; - _storagesOpt = storagesOpt; + _storageIdentifiers = storagesIdentifiers; _provider = initialDocumentation; } @@ -628,9 +631,9 @@ protected override Metadata GetMetadataImpl() => _metadata; protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties) - => new SerializedMetadataReference(properties, FilePath, _metadata, _storagesOpt, _provider); + => new SerializedMetadataReference(properties, FilePath, _metadata, _storageIdentifiers, _provider); - public IReadOnlyList? GetStorages() - => _storagesOpt.IsDefault ? null : _storagesOpt; + public IReadOnlyList? GetStorageIdentifiers() + => _storageIdentifiers; } } From 37d5b17ef9231d4f080525c7019121dc465f97d6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 19:25:45 -0700 Subject: [PATCH 139/292] Fix --- .../Test.Next/Remote/SerializationValidator.cs | 3 +-- .../Remote/SnapshotSerializationTests.cs | 16 ++++------------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index cf37f02b4b2ae..632755a0b1a82 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -94,11 +94,10 @@ public async Task GetValueAsync(Checksum checksum) var data = await GetRequiredAssetAsync(checksum).ConfigureAwait(false); Contract.ThrowIfNull(data.Value); - using var context = new SolutionReplicationContext(); using var stream = SerializableBytes.CreateWritableStream(); using (var writer = new ObjectWriter(stream, leaveOpen: true)) { - Serializer.Serialize(data.Value, writer, context, CancellationToken.None); + Serializer.Serialize(data.Value, writer, CancellationToken.None); } stream.Position = 0; diff --git a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs index e06ebecbeef94..f4f5d370c3350 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs @@ -611,11 +611,9 @@ public void TestEncodingSerialization() var serializableSourceText = new SerializableSourceText(sourceText, sourceText.GetContentHash()); using (var stream = SerializableBytes.CreateWritableStream()) { - using var context = new SolutionReplicationContext(); - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(serializableSourceText, objectWriter, context, CancellationToken.None); + serializer.Serialize(serializableSourceText, objectWriter, CancellationToken.None); } stream.Position = 0; @@ -631,11 +629,9 @@ public void TestEncodingSerialization() serializableSourceText = new SerializableSourceText(sourceText, sourceText.GetContentHash()); using (var stream = SerializableBytes.CreateWritableStream()) { - using var context = new SolutionReplicationContext(); - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(serializableSourceText, objectWriter, context, CancellationToken.None); + serializer.Serialize(serializableSourceText, objectWriter, CancellationToken.None); } stream.Position = 0; @@ -662,11 +658,9 @@ public void TestCompilationOptions_NullableAndImport() void VerifyOptions(CompilationOptions originalOptions) { using var stream = SerializableBytes.CreateWritableStream(); - using var context = new SolutionReplicationContext(); - using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(originalOptions, objectWriter, context, CancellationToken.None); + serializer.Serialize(originalOptions, objectWriter, CancellationToken.None); } stream.Position = 0; @@ -683,11 +677,9 @@ void VerifyOptions(CompilationOptions originalOptions) private static SolutionAsset CloneAsset(ISerializerService serializer, SolutionAsset asset) { using var stream = SerializableBytes.CreateWritableStream(); - using var context = new SolutionReplicationContext(); - using (var writer = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(asset.Value, writer, context, CancellationToken.None); + serializer.Serialize(asset.Value, writer, CancellationToken.None); } stream.Position = 0; From f37ede48f17a9c71cabb2cad40fd897f93dcafcb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 19:36:24 -0700 Subject: [PATCH 140/292] Fixup tests --- .../TemporaryStorageServiceTests.cs | 74 ++++++------------- 1 file changed, 23 insertions(+), 51 deletions(-) diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index df9924ce8f49a..9da9edc7d6aa8 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -51,7 +51,6 @@ public void TestTemporaryStorageStream() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var temporaryStorage = service.CreateTemporaryStreamStorage(); using var data = SerializableBytes.CreateWritableStream(); for (var i = 0; i < SharedPools.ByteBufferSize; i++) @@ -60,8 +59,9 @@ public void TestTemporaryStorageStream() } data.Position = 0; - temporaryStorage.WriteStream(data, CancellationToken.None); - using var result = temporaryStorage.ReadStream(CancellationToken.None); + var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); + + using var result = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); Assert.Equal(data.Length, result.Length); for (var i = 0; i < SharedPools.ByteBufferSize; i++) @@ -109,43 +109,21 @@ public void TestTemporaryTextStorageExceptions() Assert.Throws(() => storage.WriteTextAsync(text).Wait()); } - [ConditionalFact(typeof(WindowsOnly))] - public void TestTemporaryStreamStorageExceptions() - { - using var workspace = new AdhocWorkspace(); - var textFactory = Assert.IsType(workspace.Services.GetService()); - var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); - - // Nothing has been written yet - Assert.Throws(() => storage.ReadStream(CancellationToken.None)); - - // write a normal stream - var stream = new MemoryStream(); - stream.Write([42], 0, 1); - stream.Position = 0; - storage.WriteStream(stream, CancellationToken.None); - - // Writing multiple times is not allowed - // These should also throw before ever getting to the point where they would look at the null stream arg. - Assert.Throws(() => storage.WriteStream(null!, CancellationToken.None)); - } - [ConditionalFact(typeof(WindowsOnly))] public void TestZeroLengthStreams() { using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); // 0 length streams are allowed + TemporaryStorageHandle handle; using (var stream1 = new MemoryStream()) { - storage.WriteStream(stream1, CancellationToken.None); + handle = service.WriteToTemporaryStorage(stream1, CancellationToken.None); } - using (var stream2 = storage.ReadStream(CancellationToken.None)) + using (var stream2 = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None)) { Assert.Equal(0, stream2.Length); } @@ -168,19 +146,17 @@ public void TestTemporaryStorageMemoryMappedFileManagement() { for (var j = 1; j < 5; j++) { - using ITemporaryStreamStorageInternal storage1 = service.CreateTemporaryStreamStorage(), - storage2 = service.CreateTemporaryStreamStorage(); - var storage3 = service.CreateTemporaryStreamStorage(); // let the finalizer run for this instance + using var storage1 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1), CancellationToken.None); + using var storage2 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i), CancellationToken.None); - storage1.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1)); - storage2.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i)); - storage3.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); + // let the finalizer run for this instance + var storage3 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); await Task.Yield(); - using Stream s1 = storage1.ReadStream(), - s2 = storage2.ReadStream(), - s3 = storage3.ReadStream(CancellationToken.None); + using Stream s1 = service.ReadFromTemporaryStorageService(storage1.Identifier, CancellationToken.None); + using Stream s2 = service.ReadFromTemporaryStorageService(storage2.Identifier, CancellationToken.None); + using Stream s3 = service.ReadFromTemporaryStorageService(storage3.Identifier, CancellationToken.None); Assert.Equal(1024 * i - 1, s1.Length); Assert.Equal(1024 * i, s2.Length); Assert.Equal(1024 * i + 1, s3.Length); @@ -212,18 +188,17 @@ public void TestTemporaryStorageScaling() // Create 4GB of memory mapped files var fileCount = (int)((long)4 * 1024 * 1024 * 1024 / data.Length); - var storageHandles = new List(fileCount); + var storageHandles = new List(fileCount); for (var i = 0; i < fileCount; i++) { - var s = service.CreateTemporaryStreamStorage(); - storageHandles.Add(s); data.Position = 0; - s.WriteStream(data, CancellationToken.None); + var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); + storageHandles.Add(handle); } for (var i = 0; i < 1024 * 5; i++) { - using var s = storageHandles[i].ReadStream(CancellationToken.None); + using var s = service.ReadFromTemporaryStorageService(storageHandles[i].Identifier, CancellationToken.None); Assert.Equal(1, s.ReadByte()); storageHandles[i].Dispose(); } @@ -236,7 +211,6 @@ public void StreamTest1() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); using var expected = new MemoryStream(); for (var i = 0; i < 10000; i++) @@ -245,10 +219,10 @@ public void StreamTest1() } expected.Position = 0; - storage.WriteStream(expected, CancellationToken.None); + var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = storage.ReadStream(CancellationToken.None); + using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); Assert.Equal(expected.Length, stream.Length); for (var i = 0; i < expected.Length; i++) @@ -263,7 +237,6 @@ public void StreamTest2() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); using var expected = new MemoryStream(); for (var i = 0; i < 10000; i++) @@ -272,10 +245,10 @@ public void StreamTest2() } expected.Position = 0; - storage.WriteStream(expected, CancellationToken.None); + var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = storage.ReadStream(CancellationToken.None); + using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); Assert.Equal(expected.Length, stream.Length); var index = 0; @@ -300,7 +273,6 @@ public void StreamTest3() using var workspace = new AdhocWorkspace(); var textFactory = Assert.IsType(workspace.Services.GetService()); var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryStreamStorage(); using var expected = new MemoryStream(); var random = new Random(Environment.TickCount); @@ -314,10 +286,10 @@ public void StreamTest3() } expected.Position = 0; - storage.WriteStream(expected, CancellationToken.None); + var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = storage.ReadStream(CancellationToken.None); + using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); Assert.Equal(expected.Length, stream.Length); for (var i = 0; i < expected.Length; i++) From a02864b134cbef853a0bc0f438c03007bf418305 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 19:46:37 -0700 Subject: [PATCH 141/292] lint --- .../Core/Test.Next/Remote/SnapshotSerializationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs index f4f5d370c3350..d638391df2977 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs @@ -613,7 +613,7 @@ public void TestEncodingSerialization() { using (var objectWriter = new ObjectWriter(stream, leaveOpen: true)) { - serializer.Serialize(serializableSourceText, objectWriter, CancellationToken.None); + serializer.Serialize(serializableSourceText, objectWriter, CancellationToken.None); } stream.Position = 0; From c4bd6cf78cc90841d449fefd338c2428048577de Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:17:12 -0700 Subject: [PATCH 142/292] Docs --- .../ITemporaryStorageService.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index e35b3cfcdd73b..3b75d7170fceb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -16,9 +16,39 @@ public interface ITemporaryStorageService : IWorkspaceService ITemporaryTextStorage CreateTemporaryTextStorage(CancellationToken cancellationToken = default); } +/// +/// API to allow a client to write data to memory-mapped-file storage (allowing it to be shared across processes). +/// internal interface ITemporaryStorageServiceInternal : IWorkspaceService { + /// + /// Write the provided to a new memory-mapped-file. Returns a handle to the data that can + /// be used to identify the data across processes allowing it to be read back in in any process. Note: the data + /// will not longer be readonable if the returned is disposed. + /// + /// + /// This type is used for two purposes. + /// + /// + /// Dumping metadata to disk. This then allowing them to be read in by mapping + /// their data into types like . It also allows them to be read in by our server + /// process, without having to transmit the data over the wire. For this use case, we never dispose of the handle, + /// opting to keep things simple by having the host and server not have to coordinate on the lifetime of the data. + /// + /// + /// Dumping large compiler command lines to disk to purge them from main memory. Some of these strings are enormous + /// (many MB large), and will get into the LOH. This allows us to dump the data, knowing we can perfectly + /// reconstruct it when needed. In this case, we do dispose of the handle, as we don't need to keep the data around + /// when we get the next large compiler command line. + /// + /// + /// TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); + + /// + /// Reads the data indicated to by the into a stream. This stream can be + /// created in a different process than the one that wrote the data originally. + /// Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken); ITemporaryTextStorageInternal CreateTemporaryTextStorage(); From 3dd37c9f135148348bc5bddd9592ee9801a394bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:17:24 -0700 Subject: [PATCH 143/292] lint --- .../Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 3b75d7170fceb..2ac827f2263e2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -66,7 +66,7 @@ internal sealed class TemporaryStorageHandle(IDisposable underlyingData, Tempora private IDisposable? _underlyingData = underlyingData; private TemporaryStorageIdentifier? _identifier = identifier; - public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); + public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); public void Dispose() { From 558560e7f5995d7f612adfd8fcfba830c807243e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:20:27 -0700 Subject: [PATCH 144/292] docs --- .../VisualStudioMetadataReferenceManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 280a5ec1c30cf..80b5dd3670027 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -37,11 +37,11 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS private static readonly ConditionalWeakTable s_lifetimeMap = new(); /// - /// Mapping from an we created, to the memory mapped files (mmf) corresponding to - /// the assembly and all the modules within it. This is kept around to make OOP syncing more efficient. - /// Specifically, since we know we read the assembly into an mmf, we can just send the mmf name/offset/length to - /// the remote process, and it can map that same memory in directly, instead of needing the host to send the - /// entire contents of the assembly over the channel to the OOP process. + /// Mapping from an we created, to the identifiers identifying the memory mapped + /// files (mmf) corresponding to that assembly and all the modules within it. This is kept around to make OOP + /// syncing more efficient. Specifically, since we know we dumped the assembly into an mmf, we can just send the mmf + /// name/offset/length to the remote process, and it can map that same memory in directly, instead of needing the + /// host to send the entire contents of the assembly over the channel to the OOP process. /// private static readonly ConditionalWeakTable> s_metadataToStorageIdentifiers = new(); From 9ec14a8d70d124f5c7187f4e0bd03268f4f42b34 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:26:50 -0700 Subject: [PATCH 145/292] comments --- .../VisualStudioMetadataReferenceManager.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 80b5dd3670027..637aa33983542 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -188,9 +188,12 @@ void GetStorageInfoFromTemporaryStorage( FileKey moduleFileKey, out TemporaryStorageIdentifier storageIdentifier, out UnmanagedMemoryStream stream) { int size; + + // Create a temp stream in memory to copy the metadata bytes into. using (var copyStream = SerializableBytes.CreateWritableStream()) { - // open a file and let it go as soon as possible + // Open a file on disk, find the metadata section, copy those bytes into the temp stream, and release + // the file immediately after. using (var fileStream = FileUtilities.OpenRead(moduleFileKey.FullPath)) { var headers = new PEHeaders(fileStream); @@ -208,13 +211,15 @@ void GetStorageInfoFromTemporaryStorage( StreamCopy(fileStream, copyStream, offset, size); } - // copy over the data to temp storage and let pooled stream go + // Now, copy over the metadata bytes into a memory mapped file. This will keep it fixed in a single + // location, so we can create a metadata value wrapping that. This will also let us share the memory + // for that metadata value with our OOP process. copyStream.Position = 0; var handle = _temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); storageIdentifier = handle.Identifier; } - // get stream that owns the underlying unmanaged memory. + // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); // stream size must be same as what metadata reader said the size should be. From a32fa8e580d0f58448287b478091c0223f5cdd54 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:31:36 -0700 Subject: [PATCH 146/292] inline --- .../Serialization/SerializerService.cs | 10 +++- .../SerializerService_Reference.cs | 46 ++++--------------- 2 files changed, 17 insertions(+), 39 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 02868db1077ea..88cc09e2e4a45 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Composition; using System.Linq; +using System.Runtime.Versioning; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; @@ -16,6 +17,9 @@ namespace Microsoft.CodeAnalysis.Serialization; +#if NETCOREAPP +[SupportedOSPlatform("windows")] +#endif internal partial class SerializerService : ISerializerService { [ExportWorkspaceServiceFactory(typeof(ISerializerService), layer: ServiceLayer.Default), Shared] @@ -36,7 +40,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) private readonly SolutionServices _workspaceServices; - private readonly ITemporaryStorageServiceInternal _storageService; + private readonly TemporaryStorageService _storageService; private readonly ITextFactoryService _textService; private readonly IDocumentationProviderService? _documentationService; private readonly IAnalyzerAssemblyLoaderProvider _analyzerLoaderProvider; @@ -48,7 +52,9 @@ private protected SerializerService(SolutionServices workspaceServices) { _workspaceServices = workspaceServices; - _storageService = workspaceServices.GetRequiredService(); + // Serialization is only involved when we have a remote process. Which is only in VS. So the type of the + // storage service here is well known. + _storageService = (TemporaryStorageService)workspaceServices.GetRequiredService(); _textService = workspaceServices.GetRequiredService(); _analyzerLoaderProvider = workspaceServices.GetRequiredService(); _documentationService = workspaceServices.GetService(); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index eecf1079e1e95..f7392defd3a4e 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -409,9 +409,15 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); Contract.ThrowIfFalse(length == storageStream.Length); - var metadata = GetMetadata(storageStream, length); - - return (metadata, storageIdentifier); + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. + unsafe + { + var metadata = ModuleMetadata.CreateFromMetadata( + (IntPtr)storageStream.PositionPointer, (int)storageStream.Length, storageStream.Dispose); + return (metadata, storageIdentifier); + } } private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) @@ -459,40 +465,6 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial } } - private static ModuleMetadata GetMetadata(Stream stream, long length) - { - if (stream is UnmanagedMemoryStream unmanagedStream) - { - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. - unsafe - { - return ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - } - } - - PinnedObject pinnedObject; - if (stream is MemoryStream memory && - memory.TryGetBuffer(out var buffer) && - buffer.Offset == 0) - { - pinnedObject = new PinnedObject(buffer.Array!); - } - else - { - var array = new byte[length]; - stream.Read(array, 0, (int)length); - pinnedObject = new PinnedObject(array); - } - - // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in - // as the onDispose callback of the metadata. - return ModuleMetadata.CreateFromMetadata( - pinnedObject.GetPointer(), (int)length, pinnedObject.Dispose); - } - private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); From 8c564823fc5bf2b5cfad7e31326eb42334631c9f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:40:52 -0700 Subject: [PATCH 147/292] Specialize for windows --- .../TestTemporaryStorageServiceFactory.cs | 42 +++++++++---------- .../SolutionTests/SolutionTestHelpers.cs | 9 +--- .../CoreTest/SyntaxReferenceTests.cs | 8 +--- .../CoreTest/TestCompositionTests.cs | 19 ++++++++- 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs index 21147f43736bd..74e33409cdb15 100644 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs +++ b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs @@ -1,24 +1,24 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. +//// See the LICENSE file in the project root for more information. -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; +//using System; +//using System.Composition; +//using Microsoft.CodeAnalysis.Host; +//using Microsoft.CodeAnalysis.Host.Mef; -namespace Microsoft.CodeAnalysis.UnitTests.Persistence -{ - [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestTemporaryStorageServiceFactory() - { - } +//namespace Microsoft.CodeAnalysis.UnitTests.Persistence +//{ +// [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] +// internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory +// { +// [ImportingConstructor] +// [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +// public TestTemporaryStorageServiceFactory() +// { +// } - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => TrivialTemporaryStorageService.Instance; - } -} +// public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) +// => TrivialTemporaryStorageService.Instance; +// } +//} diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs index 0cdc9229c893f..0ae9c1400866d 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests @@ -17,17 +16,11 @@ internal static class SolutionTestHelpers public static Workspace CreateWorkspace(Type[]? additionalParts = null, TestHost testHost = TestHost.InProcess) => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).WithTestHostParts(testHost).GetHostServices()); - public static Workspace CreateWorkspaceWithNormalText() - => CreateWorkspace( - [ - typeof(TestTemporaryStorageServiceFactory) - ]); - public static Workspace CreateWorkspaceWithRecoverableText() => CreateWorkspace(); public static Workspace CreateWorkspaceWithPartialSemantics(TestHost testHost = TestHost.InProcess) - => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics([typeof(TestTemporaryStorageServiceFactory)], testHost); + => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics(testHost: testHost); #nullable disable diff --git a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs index 2ce0a35a4bb70..68323e087a197 100644 --- a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs +++ b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs @@ -6,11 +6,8 @@ using System; using System.Linq; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Roslyn.Test.Utilities; using Xunit; using CS = Microsoft.CodeAnalysis.CSharp; @@ -26,10 +23,7 @@ private static Workspace CreateWorkspace(Type[] additionalParts = null) => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).GetHostServices()); private static Workspace CreateWorkspaceWithRecoverableSyntaxTrees() - => CreateWorkspace( - [ - typeof(TestTemporaryStorageServiceFactory) - ]); + => CreateWorkspace(); private static Solution AddSingleFileCSharpProject(Solution solution, string source) { diff --git a/src/Workspaces/CoreTest/TestCompositionTests.cs b/src/Workspaces/CoreTest/TestCompositionTests.cs index 13237a0dcb699..32535180dc45a 100644 --- a/src/Workspaces/CoreTest/TestCompositionTests.cs +++ b/src/Workspaces/CoreTest/TestCompositionTests.cs @@ -2,14 +2,29 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests { + [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] + internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestTemporaryStorageServiceFactory() + { + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => TrivialTemporaryStorageService.Instance; + } + public class TestCompositionTests { [Fact] From c36d7f56573ce372dc9e9e78ce4cc187bc2e7b43 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:46:17 -0700 Subject: [PATCH 148/292] Doc --- .../Core/Portable/Serialization/ISupportTemporaryStorage.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index d0d18406c8333..757551a84198d 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -8,8 +8,9 @@ namespace Microsoft.CodeAnalysis.Serialization; /// -/// This lets consumer to get to inner temporary storage that references use -/// as its shadow copy storage +/// Interface for services that support dumping their contents to memory-mapped-files (generally speaking, our assembly +/// reference objects). This allows those objects to expose the memory-mapped-file info needed to read that data back +/// in in any process. /// internal interface ISupportTemporaryStorage { From f00c59f2f6d3d42d6968e72818cf387328c86929 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:48:21 -0700 Subject: [PATCH 149/292] null or empty --- .../Core/Portable/Serialization/SerializerService_Reference.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f7392defd3a4e..02e01f40211b2 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -313,7 +313,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT ISupportTemporaryStorage reference, ObjectWriter writer, CancellationToken cancellationToken) { var storageIdentifiers = reference.GetStorageIdentifiers(); - if (storageIdentifiers == null) + if (storageIdentifiers is null || storageIdentifiers.Count == 0) return false; WritePortableExecutableReferenceHeaderTo((PortableExecutableReference)reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); From a1bfa7bff730c386dd03e914c49413f4301ad26c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:50:28 -0700 Subject: [PATCH 150/292] In progress --- .../SerializerService_Reference.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 02e01f40211b2..8c1281964bd46 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -362,11 +362,13 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return (AssemblyMetadata.Create(pooledMetadata.Object), storageIdentifiers: []); } - - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); + else + { + Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); #pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: []); + return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: []); #pragma warning restore CA2016 + } } if (metadataKind == MetadataImageKind.Assembly) @@ -388,11 +390,13 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return (AssemblyMetadata.Create(pooledMetadata.Object), pooledStorageIdentifiers.Object.ToImmutableArrayOrEmpty()); } + else + { + Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - - var moduleInfo = ReadModuleMetadataFrom(reader, kind, cancellationToken); - return (moduleInfo.metadata, [moduleInfo.storageIdentifier]); + var moduleInfo = ReadModuleMetadataFrom(reader, kind, cancellationToken); + return (moduleInfo.metadata, [moduleInfo.storageIdentifier]); + } } private (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFrom( From ca76aa88369cdeaae397dc76b363284b23a28e68 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:51:03 -0700 Subject: [PATCH 151/292] Simplify --- .../SerializerService_Reference.cs | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 8c1281964bd46..41bf453acb1cb 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -343,34 +343,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT } var metadataKind = (MetadataImageKind)imageKind; - if (_storageService == null) - { - if (metadataKind == MetadataImageKind.Assembly) - { - using var pooledMetadata = Creator.CreateList(); - - var count = reader.ReadInt32(); - for (var i = 0; i < count; i++) - { - metadataKind = (MetadataImageKind)reader.ReadInt32(); - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - -#pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - pooledMetadata.Object.Add(ReadModuleMetadataFrom(reader, kind)); -#pragma warning restore CA2016 - } - - return (AssemblyMetadata.Create(pooledMetadata.Object), storageIdentifiers: []); - } - else - { - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); -#pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storageIdentifiers: []); -#pragma warning restore CA2016 - } - } - if (metadataKind == MetadataImageKind.Assembly) { using var pooledMetadata = Creator.CreateList(); From 8141930bee4f536a91f3fa0d7d165e9afce92f4b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:56:11 -0700 Subject: [PATCH 152/292] simplify --- ...CompilationState.SkeletonReferenceCache.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index ef93f263e5108..5a80a4c713714 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -218,17 +218,10 @@ public readonly SkeletonReferenceCache Clone() private static SkeletonReferenceSet? CreateSkeletonSet( SolutionServices services, Compilation compilation, CancellationToken cancellationToken) { - var temporaryStorageService = services.GetRequiredService(); - - var handle = TryCreateMetadataStorage(); - if (handle == null) + var metadata = TryCreateMetadata(); + if (metadata == null) return null; - // Now read the data back from the stream from the memory mapped file. This will come back as an - // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. - var metadata = AssemblyMetadata.CreateFromStream( - temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); - // read in the stream and pass ownership of it to the metadata object. When it is disposed it will dispose // the stream as well. return new SkeletonReferenceSet( @@ -236,7 +229,7 @@ public readonly SkeletonReferenceCache Clone() compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - TemporaryStorageHandle? TryCreateMetadataStorage() + AssemblyMetadata? TryCreateMetadata() { cancellationToken.ThrowIfCancellationRequested(); @@ -261,9 +254,14 @@ public readonly SkeletonReferenceCache Clone() // assembly-metadata point directly to that pointer in memory, instead of it having to make its // own copy it needs to own the lifetime of. stream.Position = 0; + + var temporaryStorageService = services.GetRequiredService(); var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); - return handle; + // Now read the data back from the stream from the memory mapped file. This will come back as an + // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. + return AssemblyMetadata.CreateFromStream( + temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); } if (logger != null) From 4bf45a4ab4801b5d03d18f5bc21fe341385600b0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 20:58:24 -0700 Subject: [PATCH 153/292] remove --- .../TestTemporaryStorageServiceFactory.cs | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs deleted file mode 100644 index 74e33409cdb15..0000000000000 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. -//// See the LICENSE file in the project root for more information. - -//using System; -//using System.Composition; -//using Microsoft.CodeAnalysis.Host; -//using Microsoft.CodeAnalysis.Host.Mef; - -//namespace Microsoft.CodeAnalysis.UnitTests.Persistence -//{ -// [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] -// internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory -// { -// [ImportingConstructor] -// [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -// public TestTemporaryStorageServiceFactory() -// { -// } - -// public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) -// => TrivialTemporaryStorageService.Instance; -// } -//} From 8f9a9a5e7b0d3dc78493ce11fc8ca069414428aa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 21:00:42 -0700 Subject: [PATCH 154/292] Simplify --- .../Solution/SolutionCompilationState.SkeletonReferenceCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 5a80a4c713714..1700598bcb8d6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -234,6 +234,7 @@ public readonly SkeletonReferenceCache Clone() cancellationToken.ThrowIfCancellationRequested(); var logger = services.GetService(); + var temporaryStorageService = services.GetRequiredService(); try { @@ -255,7 +256,6 @@ public readonly SkeletonReferenceCache Clone() // own copy it needs to own the lifetime of. stream.Position = 0; - var temporaryStorageService = services.GetRequiredService(); var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); // Now read the data back from the stream from the memory mapped file. This will come back as an From 71b3ea5b51f750cd327d7b1003cdf18e6c7ba7ef Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 21:08:00 -0700 Subject: [PATCH 155/292] Simplify --- .../SerializerService_Reference.cs | 28 +++++---- ...CompilationState.SkeletonReferenceCache.cs | 63 ++++++++++--------- 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 41bf453acb1cb..4dac07640da70 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -377,13 +377,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT cancellationToken.ThrowIfCancellationRequested(); // Get the storage identifier for the module metadata. - var (storageIdentifier, length) = GetTemporaryStorageIdentifier(reader, kind, cancellationToken); - - // Now read in the module data using that identifier. This will either be reading from the host's memory if - // they passed us the information about that memory segment. Or it will be reading from our own memory if they - // sent us the full contents. - var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(length == storageStream.Length); + var (storageIdentifier, storageStream) = GetTemporaryStorageData(reader, kind, cancellationToken); // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of @@ -411,11 +405,13 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); } - private (TemporaryStorageIdentifier handle, long length) GetTemporaryStorageIdentifier( + private (TemporaryStorageIdentifier identifier, UnmanagedMemoryStream storageStream) GetTemporaryStorageData( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); + long length; + TemporaryStorageIdentifier storageIdentifier; if (kind == SerializationKinds.Bits) { // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the @@ -423,12 +419,11 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial using var stream = SerializableBytes.CreateWritableStream(); CopyByteArrayToStream(reader, stream, cancellationToken); - var length = stream.Length; + length = stream.Length; stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); - - return (storageHandle.Identifier, length); + storageIdentifier = storageHandle.Identifier; } else { @@ -436,9 +431,16 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial // will not be released by the host. var name = reader.ReadRequiredString(); var offset = reader.ReadInt64(); - var size = reader.ReadInt64(); - return (new TemporaryStorageIdentifier(name, offset, size), size); + length = reader.ReadInt64(); + storageIdentifier = new TemporaryStorageIdentifier(name, offset, length); } + + // Now read in the module data using that identifier. This will either be reading from the host's memory if + // they passed us the information about that memory segment. Or it will be reading from our own memory if they + // sent us the full contents. + var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + Contract.ThrowIfFalse(length == storageStream.Length); + return (storageIdentifier, storageStream); } private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 1700598bcb8d6..6c2c4f62f6b52 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -242,48 +242,53 @@ public readonly SkeletonReferenceCache Clone() using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) { - using var stream = SerializableBytes.CreateWritableStream(); - - // First, emit the data to an in-memory stream. - var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); - - if (emitResult.Success) + TemporaryStorageHandle? handle = null; + EmitResult emitResult; + using (var stream = SerializableBytes.CreateWritableStream()) { - logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + // First, emit the data to an in-memory stream. + emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); - // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the - // assembly-metadata point directly to that pointer in memory, instead of it having to make its - // own copy it needs to own the lifetime of. - stream.Position = 0; - - var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); + if (emitResult.Success) + { + // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the + // assembly-metadata point directly to that pointer in memory, instead of it having to make its + // own copy it needs to own the lifetime of. + stream.Position = 0; + handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); + } + } + if (handle != null) + { // Now read the data back from the stream from the memory mapped file. This will come back as an // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. return AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); } - - if (logger != null) + else { - logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); - - foreach (var diagnostic in emitResult.Diagnostics) + if (logger != null) { - logger.Log(" " + diagnostic.GetMessage()); + logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + + foreach (var diagnostic in emitResult.Diagnostics) + { + logger.Log(" " + diagnostic.GetMessage()); + } } - } - // log emit failures so that we can improve most common cases - Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => - { - // log errors in the format of - // CS0001:1;CS002:10;... - var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); - m["Errors"] = string.Join(";", groups); - })); + // log emit failures so that we can improve most common cases + Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => + { + // log errors in the format of + // CS0001:1;CS002:10;... + var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); + m["Errors"] = string.Join(";", groups); + })); - return null; + return null; + } } } finally From 72a4937ca29421b58e13022decb46d2900b09686 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 21:10:30 -0700 Subject: [PATCH 156/292] Add back logging --- .../Solution/SolutionCompilationState.SkeletonReferenceCache.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 6c2c4f62f6b52..ae82c81236ce3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -261,6 +261,8 @@ public readonly SkeletonReferenceCache Clone() if (handle != null) { + logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + // Now read the data back from the stream from the memory mapped file. This will come back as an // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. return AssemblyMetadata.CreateFromStream( From 2f371042b9c01551e62883487a973f4936c9922f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:16:17 -0700 Subject: [PATCH 157/292] use property' git push --- .../VisualStudioMetadataReference.Snapshot.cs | 2 +- .../Serialization/ISupportTemporaryStorage.cs | 2 +- .../SerializerService_Reference.cs | 29 +++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 98423db28fab4..711aa80dcd26e 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList GetStorageIdentifiers() + public IReadOnlyList StorageIdentifiers => _provider.GetStorageIdentifiers(this.FilePath, _timestamp.Value); } } diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index 757551a84198d..d622b8fd39162 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -14,5 +14,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? GetStorageIdentifiers(); + IReadOnlyList? StorageIdentifiers { get; }; } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 4dac07640da70..f63af77354508 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -62,17 +62,16 @@ public static Checksum CreateChecksum(AnalyzerReference reference, CancellationT public virtual void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { - if (reference is PortableExecutableReference portable) + if (reference is PortableExecutableReference peReference) { - if (portable is ISupportTemporaryStorage supportTemporaryStorage) + if (peReference is ISupportTemporaryStorage { StorageIdentifiers: { Count: > 0 } storageIdentifiers } && + TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( + peReference, storageIdentifiers, writer, cancellationToken)) { - if (TryWritePortableExecutableReferenceBackedByTemporaryStorageTo(supportTemporaryStorage, writer, cancellationToken)) - { - return; - } + return; } - WritePortableExecutableReferenceTo(portable, writer, cancellationToken); + WritePortableExecutableReferenceTo(peReference, writer, cancellationToken); return; } @@ -310,13 +309,14 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio } private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( - ISupportTemporaryStorage reference, ObjectWriter writer, CancellationToken cancellationToken) + PortableExecutableReference reference, + IReadOnlyList storageIdentifiers, + ObjectWriter writer, + CancellationToken cancellationToken) { - var storageIdentifiers = reference.GetStorageIdentifiers(); - if (storageIdentifiers is null || storageIdentifiers.Count == 0) - return false; + Contract.ThrowIfTrue(storageIdentifiers.Count == 0); - WritePortableExecutableReferenceHeaderTo((PortableExecutableReference)reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); + WritePortableExecutableReferenceHeaderTo(reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); writer.WriteInt32((int)MetadataImageKind.Assembly); writer.WriteInt32(storageIdentifiers.Count); @@ -556,6 +556,8 @@ private sealed class SerializedMetadataReference : PortableExecutableReference, private readonly ImmutableArray _storageIdentifiers; private readonly DocumentationProvider _provider; + public IReadOnlyList StorageIdentifiers => _storageIdentifiers; + public SerializedMetadataReference( MetadataReferenceProperties properties, string? fullPath, @@ -582,8 +584,5 @@ protected override Metadata GetMetadataImpl() protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties) => new SerializedMetadataReference(properties, FilePath, _metadata, _storageIdentifiers, _provider); - - public IReadOnlyList? GetStorageIdentifiers() - => _storageIdentifiers; } } From a8b0152cf73976103f204fa50dff3dfe08d98b28 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:19:48 -0700 Subject: [PATCH 158/292] Cleanup --- .../Core/Portable/Serialization/ISupportTemporaryStorage.cs | 2 +- .../Workspace/Host/TemporaryStorage/ITemporaryStorage.cs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index d622b8fd39162..2eaee88847d46 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -14,5 +14,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? StorageIdentifiers { get; }; + IReadOnlyList? StorageIdentifiers { get; } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index 0e51a34da6993..6ae03ba1b3a0c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -40,9 +40,3 @@ internal interface ITemporaryTextStorageInternal : IDisposable void WriteText(SourceText text, CancellationToken cancellationToken = default); Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); } - -//internal interface ITemporaryStreamStorageInternal : IDisposable -//{ -// Stream ReadStream(CancellationToken cancellationToken = default); -// void WriteStream(Stream stream, CancellationToken cancellationToken = default); -//} From a5106195698fb700b73300e6c9fc9d831adf5798 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:22:49 -0700 Subject: [PATCH 159/292] inline more --- .../ProjectSystemProjectOptionsProcessor.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 04fcee98897cd..b2ea809fa6420 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -250,20 +250,13 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) // includes in the IDE so we can be watching for changes again. var commandLine = _commandLineStorageHandle == null ? ImmutableArray.Empty - : ReadLines(_temporaryStorageService, _commandLineStorageHandle.Identifier); + : EnumerateLines(_temporaryStorageService, _commandLineStorageHandle.Identifier).ToImmutableArray(); DisposeOfRuleSetFile_NoLock(); ReparseCommandLine_NoLock(commandLine); UpdateProjectOptions_NoLock(); } - static ImmutableArray ReadLines( - ITemporaryStorageServiceInternal temporaryStorageService, - TemporaryStorageIdentifier storageIdentifier) - { - return EnumerateLines(temporaryStorageService, storageIdentifier).ToImmutableArray(); - } - static IEnumerable EnumerateLines( ITemporaryStorageServiceInternal temporaryStorageService, TemporaryStorageIdentifier storageIdentifier) From c9542befb89fa830f1e63cdded821b7da4ed3146 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:24:43 -0700 Subject: [PATCH 160/292] simplify --- ...CompilationState.SkeletonReferenceCache.cs | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index ae82c81236ce3..d3424a78b21a9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -242,55 +242,46 @@ public readonly SkeletonReferenceCache Clone() using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_EmitMetadataOnlyImage, cancellationToken)) { - TemporaryStorageHandle? handle = null; - EmitResult emitResult; - using (var stream = SerializableBytes.CreateWritableStream()) - { - // First, emit the data to an in-memory stream. - emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); + // First, emit the data to an in-memory stream. + using var stream = SerializableBytes.CreateWritableStream(); + var emitResult = compilation.Emit(stream, options: s_metadataOnlyEmitOptions, cancellationToken: cancellationToken); - if (emitResult.Success) - { - // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the - // assembly-metadata point directly to that pointer in memory, instead of it having to make its - // own copy it needs to own the lifetime of. - stream.Position = 0; - handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); - } - } - - if (handle != null) + if (emitResult.Success) { logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the + // assembly-metadata point directly to that pointer in memory, instead of it having to make its + // own copy it needs to own the lifetime of. + stream.Position = 0; + var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); + // Now read the data back from the stream from the memory mapped file. This will come back as an // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. return AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); } - else + + if (logger != null) { - if (logger != null) - { - logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); + logger.Log($"Failed to create a skeleton assembly for {compilation.AssemblyName}:"); - foreach (var diagnostic in emitResult.Diagnostics) - { - logger.Log(" " + diagnostic.GetMessage()); - } + foreach (var diagnostic in emitResult.Diagnostics) + { + logger.Log(" " + diagnostic.GetMessage()); } + } - // log emit failures so that we can improve most common cases - Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => - { - // log errors in the format of - // CS0001:1;CS002:10;... - var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); - m["Errors"] = string.Join(";", groups); - })); + // log emit failures so that we can improve most common cases + Logger.Log(FunctionId.MetadataOnlyImage_EmitFailure, KeyValueLogMessage.Create(m => + { + // log errors in the format of + // CS0001:1;CS002:10;... + var groups = emitResult.Diagnostics.GroupBy(d => d.Id).Select(g => $"{g.Key}:{g.Count()}"); + m["Errors"] = string.Join(";", groups); + })); - return null; - } + return null; } } finally From b90ac79ccb502b9c5473c8c421a85f365b10b7e3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:26:30 -0700 Subject: [PATCH 161/292] simplify --- .../SolutionCompilationState.SkeletonReferenceCache.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index d3424a78b21a9..966a904322b92 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -234,7 +234,6 @@ public readonly SkeletonReferenceCache Clone() cancellationToken.ThrowIfCancellationRequested(); var logger = services.GetService(); - var temporaryStorageService = services.GetRequiredService(); try { @@ -250,6 +249,8 @@ public readonly SkeletonReferenceCache Clone() { logger?.Log($"Successfully emitted a skeleton assembly for {compilation.AssemblyName}"); + var temporaryStorageService = services.GetRequiredService(); + // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the // assembly-metadata point directly to that pointer in memory, instead of it having to make its // own copy it needs to own the lifetime of. From 94a1ff5d56d95de7ccd83af8282e4ec0009dbc19 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:33:16 -0700 Subject: [PATCH 162/292] simplify --- .../SerializerService_Reference.cs | 13 ++++--------- .../ITemporaryStorageService.cs | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f63af77354508..dc5d76476b132 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -321,12 +321,10 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT writer.WriteInt32((int)MetadataImageKind.Assembly); writer.WriteInt32(storageIdentifiers.Count); - foreach (var (name, offset, size) in storageIdentifiers) + foreach (var identifier in storageIdentifiers) { writer.WriteInt32((int)MetadataImageKind.Module); - writer.WriteString(name); - writer.WriteInt64(offset); - writer.WriteInt64(size); + identifier.WriteTo(writer); } return true; @@ -429,17 +427,14 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial { // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. - var name = reader.ReadRequiredString(); - var offset = reader.ReadInt64(); - length = reader.ReadInt64(); - storageIdentifier = new TemporaryStorageIdentifier(name, offset, length); + storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); } // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(length == storageStream.Length); + Contract.ThrowIfFalse(storageIdentifier.Size == storageStream.Length); return (storageIdentifier, storageStream); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 2ac827f2263e2..213e791f91d55 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.Serialization; using System.Threading; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -84,4 +85,18 @@ public void Dispose() internal sealed record TemporaryStorageIdentifier( [property: DataMember(Order = 0)] string Name, [property: DataMember(Order = 1)] long Offset, - [property: DataMember(Order = 2)] long Size); + [property: DataMember(Order = 2)] long Size) +{ + public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) + => new( + reader.ReadRequiredString(), + reader.ReadInt64(), + reader.ReadInt64()); + + public void WriteTo(ObjectWriter writer) + { + writer.WriteString(Name); + writer.WriteInt64(Offset); + writer.WriteInt64(Size); + } +} From 9994a4d175cde45153d5710998cabd4c0cfe090d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:36:26 -0700 Subject: [PATCH 163/292] Inlinem ethod' --- .../SerializerService_Reference.cs | 58 ++++++++----------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index dc5d76476b132..22ecd14440a44 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -374,38 +374,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT { cancellationToken.ThrowIfCancellationRequested(); - // Get the storage identifier for the module metadata. - var (storageIdentifier, storageStream) = GetTemporaryStorageData(reader, kind, cancellationToken); - - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. - unsafe - { - var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)storageStream.PositionPointer, (int)storageStream.Length, storageStream.Dispose); - return (metadata, storageIdentifier); - } - } - - private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) - { - Contract.ThrowIfFalse(SerializationKinds.Bits == kind); - - var array = reader.ReadByteArray(); - - // Pin the array so that the module metadata can treat it as a segment of unmanaged memory. - var pinnedObject = new PinnedObject(array); - - // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in - // as the onDispose callback of the metadata. - return ModuleMetadata.CreateFromMetadata( - pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); - } - - private (TemporaryStorageIdentifier identifier, UnmanagedMemoryStream storageStream) GetTemporaryStorageData( - ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) - { Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); long length; @@ -435,7 +403,31 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial // sent us the full contents. var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); Contract.ThrowIfFalse(storageIdentifier.Size == storageStream.Length); - return (storageIdentifier, storageStream); + + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. + unsafe + { + var metadata = ModuleMetadata.CreateFromMetadata( + (IntPtr)storageStream.PositionPointer, (int)storageStream.Length, storageStream.Dispose); + return (metadata, storageIdentifier); + } + } + + private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) + { + Contract.ThrowIfFalse(SerializationKinds.Bits == kind); + + var array = reader.ReadByteArray(); + + // Pin the array so that the module metadata can treat it as a segment of unmanaged memory. + var pinnedObject = new PinnedObject(array); + + // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in + // as the onDispose callback of the metadata. + return ModuleMetadata.CreateFromMetadata( + pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); } private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) From 3dabbaed90ac3575a1ebb134679b4aa9b6398393 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:37:24 -0700 Subject: [PATCH 164/292] extract types --- .../ITemporaryStorageService.cs | 48 ------------------- .../TemporaryStorageHandle.cs | 30 ++++++++++++ .../TemporaryStorageIdentifier.cs | 32 +++++++++++++ 3 files changed, 62 insertions(+), 48 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 213e791f91d55..26efdb3f3417c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -4,9 +4,7 @@ using System; using System.IO; -using System.Runtime.Serialization; using System.Threading; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -54,49 +52,3 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService ITemporaryTextStorageInternal CreateTemporaryTextStorage(); } - -/// -/// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is -/// not disposed, the data should remain in storage and can be readable from any process using the information provided -/// in . Use to write -/// the data to temporary storage and get a handle to it. Use to read the data back in any process. -/// -internal sealed class TemporaryStorageHandle(IDisposable underlyingData, TemporaryStorageIdentifier identifier) : IDisposable -{ - private IDisposable? _underlyingData = underlyingData; - private TemporaryStorageIdentifier? _identifier = identifier; - - public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); - - public void Dispose() - { - var data = Interlocked.Exchange(ref _underlyingData, null); - data?.Dispose(); - _identifier = null; - } -} - -/// -/// Identifier for a stream of data placed in a segment of temporary storage (generally a memory mapped file). Can be -/// used to identify that segment across processes, allowing for efficient sharing of data. -/// -[DataContract] -internal sealed record TemporaryStorageIdentifier( - [property: DataMember(Order = 0)] string Name, - [property: DataMember(Order = 1)] long Offset, - [property: DataMember(Order = 2)] long Size) -{ - public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) - => new( - reader.ReadRequiredString(), - reader.ReadInt64(), - reader.ReadInt64()); - - public void WriteTo(ObjectWriter writer) - { - writer.WriteString(Name); - writer.WriteInt64(Offset); - writer.WriteInt64(Size); - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs new file mode 100644 index 0000000000000..11b2f4976cdb7 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is +/// not disposed, the data should remain in storage and can be readable from any process using the information provided +/// in . Use to write +/// the data to temporary storage and get a handle to it. Use to read the data back in any process. +/// +internal sealed class TemporaryStorageHandle(IDisposable underlyingData, TemporaryStorageIdentifier identifier) : IDisposable +{ + private IDisposable? _underlyingData = underlyingData; + private TemporaryStorageIdentifier? _identifier = identifier; + + public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); + + public void Dispose() + { + var data = Interlocked.Exchange(ref _underlyingData, null); + data?.Dispose(); + _identifier = null; + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs new file mode 100644 index 0000000000000..23b84ece0e996 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Identifier for a stream of data placed in a segment of temporary storage (generally a memory mapped file). Can be +/// used to identify that segment across processes, allowing for efficient sharing of data. +/// +[DataContract] +internal sealed record TemporaryStorageIdentifier( + [property: DataMember(Order = 0)] string Name, + [property: DataMember(Order = 1)] long Offset, + [property: DataMember(Order = 2)] long Size) +{ + public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) + => new( + reader.ReadRequiredString(), + reader.ReadInt64(), + reader.ReadInt64()); + + public void WriteTo(ObjectWriter writer) + { + writer.WriteString(Name); + writer.WriteInt64(Offset); + writer.WriteInt64(Size); + } +} From 9efebbafe2061730d1e968f3e49e8d2264f5ae93 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:40:08 -0700 Subject: [PATCH 165/292] revert --- .../Portable/Serialization/SerializerService_Reference.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 22ecd14440a44..7f10c1f1e0902 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -62,16 +62,16 @@ public static Checksum CreateChecksum(AnalyzerReference reference, CancellationT public virtual void WriteMetadataReferenceTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken) { - if (reference is PortableExecutableReference peReference) + if (reference is PortableExecutableReference portable) { - if (peReference is ISupportTemporaryStorage { StorageIdentifiers: { Count: > 0 } storageIdentifiers } && + if (portable is ISupportTemporaryStorage { StorageIdentifiers: { Count: > 0 } storageIdentifiers } && TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( - peReference, storageIdentifiers, writer, cancellationToken)) + portable, storageIdentifiers, writer, cancellationToken)) { return; } - WritePortableExecutableReferenceTo(peReference, writer, cancellationToken); + WritePortableExecutableReferenceTo(portable, writer, cancellationToken); return; } From 5551c51a211c0c15afed1a1290b5751c7fb30795 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:42:17 -0700 Subject: [PATCH 166/292] rename --- .../Portable/Serialization/SerializerService_Reference.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 7f10c1f1e0902..25ccba1f9b3a4 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -401,8 +401,8 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. - var storageStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(storageIdentifier.Size == storageStream.Length); + var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of @@ -410,7 +410,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT unsafe { var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)storageStream.PositionPointer, (int)storageStream.Length, storageStream.Dispose); + (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); return (metadata, storageIdentifier); } } From ff94efe77ea20063a6e497d6f6ef099ca95f64ca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 19 Apr 2024 23:51:14 -0700 Subject: [PATCH 167/292] remove --- .../CoreTest/TestCompositionTests.cs | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/Workspaces/CoreTest/TestCompositionTests.cs b/src/Workspaces/CoreTest/TestCompositionTests.cs index 32535180dc45a..24535fa007888 100644 --- a/src/Workspaces/CoreTest/TestCompositionTests.cs +++ b/src/Workspaces/CoreTest/TestCompositionTests.cs @@ -2,36 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests { - [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestTemporaryStorageServiceFactory() - { - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => TrivialTemporaryStorageService.Instance; - } + using static SourceGeneratorTelemetryCollectorWorkspaceServiceTests; public class TestCompositionTests { [Fact] public void FactoryReuse() { - var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestErrorReportingService), typeof(TestTemporaryStorageServiceFactory)); - var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestTemporaryStorageServiceFactory), typeof(TestErrorReportingService)); + var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestErrorReportingService), typeof(TestSourceGeneratorTelemetryCollectorWorkspaceServiceFactory)); + var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestSourceGeneratorTelemetryCollectorWorkspaceServiceFactory), typeof(TestErrorReportingService)); Assert.Same(composition1.ExportProviderFactory, composition2.ExportProviderFactory); } From 07c8ed9b74feaa86b67623dbae1da56a0338ccfc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 00:13:38 -0700 Subject: [PATCH 168/292] Fix --- .../CoreTestUtilities/Remote/TestSerializerService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs index bf86ae8934251..9699b356e4d93 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs @@ -10,6 +10,7 @@ using System.Collections.Immutable; using System.Composition; using System.Linq; +using System.Runtime.Versioning; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; @@ -21,6 +22,9 @@ namespace Microsoft.CodeAnalysis.UnitTests.Remote { +#if NETCOREAPP + [SupportedOSPlatform("windows")] +#endif internal sealed class TestSerializerService : SerializerService { private static readonly ImmutableDictionary s_wellKnownReferenceNames = ImmutableDictionary.Create(ReferenceEqualityComparer.Instance) From 3d000a7ccbf90d41a9298d087978d493b42a8718 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 00:18:12 -0700 Subject: [PATCH 169/292] Fix --- .../CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs index 7f6cfb699835d..e216af1473e25 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemoteHostClientProvider.cs @@ -17,6 +17,7 @@ namespace Microsoft.CodeAnalysis.Remote.Testing { +#pragma warning disable CA1416 // Validate platform compatibility internal sealed class InProcRemoteHostClientProvider : IRemoteHostClientProvider, IDisposable { [ExportWorkspaceServiceFactory(typeof(IRemoteHostClientProvider), ServiceLayer.Test), Shared, PartNotDiscoverable] @@ -99,4 +100,5 @@ public void Dispose() public Task TryGetRemoteHostClientAsync(CancellationToken cancellationToken) => Task.FromResult(_lazyClient.Value); } +#pragma warning restore CA1416 // Validate platform compatibility } From 7d920a1d04bc5817f14a42fd89096fd9badc9c23 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Sat, 20 Apr 2024 10:17:48 -0700 Subject: [PATCH 170/292] Reduce allocations due to multiple levels of ImmutableArray created during find references invocations. (#73118) Reduce allocations due to multiple levels of ImmutableArray created during find references invocations. This is very similar to this PR (https://github.com/dotnet/roslyn/pull/73093) that did the same thing for ImmutableArray in this context. --- .../DelegateInvokeMethodReferenceFinder.cs | 27 ++- .../FindReferencesSearchEngine.cs | 17 +- ...sSearchEngine_FindReferencesInDocuments.cs | 11 +- .../AbstractMemberScopedReferenceFinder.cs | 21 +- ...dOrPropertyOrEventSymbolReferenceFinder.cs | 2 - .../Finders/AbstractReferenceFinder.cs | 181 +++++++++--------- ...tractReferenceFinder_GlobalSuppressions.cs | 18 +- ...tractTypeParameterSymbolReferenceFinder.cs | 28 +-- ...tructorInitializerSymbolReferenceFinder.cs | 8 +- .../ConstructorSymbolReferenceFinder.cs | 83 ++++---- .../DestructorSymbolReferenceFinder.cs | 7 +- .../Finders/EventSymbolReferenceFinder.cs | 6 +- ...ExplicitConversionSymbolReferenceFinder.cs | 11 +- .../ExplicitInterfaceMethodReferenceFinder.cs | 7 +- .../Finders/FieldSymbolReferenceFinder.cs | 13 +- .../Finders/IReferenceFinder.cs | 4 +- .../Finders/NamedTypeSymbolReferenceFinder.cs | 69 ++++--- .../Finders/NamespaceSymbolReferenceFinder.cs | 46 +++-- .../Finders/OperatorSymbolReferenceFinder.cs | 14 +- .../Finders/OrdinaryMethodReferenceFinder.cs | 34 ++-- .../Finders/ParameterSymbolReferenceFinder.cs | 6 +- .../PropertyAccessorSymbolReferenceFinder.cs | 33 ++-- .../Finders/PropertySymbolReferenceFinder.cs | 67 ++++--- .../FindReferences/StandardCallbacks.cs | 18 ++ 24 files changed, 411 insertions(+), 320 deletions(-) create mode 100644 src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs diff --git a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs index 8b1bd852308cb..fab481046098c 100644 --- a/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs +++ b/src/Features/Core/Portable/ChangeSignature/DelegateInvokeMethodReferenceFinder.cs @@ -76,9 +76,11 @@ protected override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -89,7 +91,12 @@ protected override async ValueTask> FindReference var root = state.Root; var nodes = root.DescendantNodes(); - using var _ = ArrayBuilder.GetInstance(out var convertedAnonymousFunctions); + var invocations = nodes.Where(syntaxFacts.IsInvocationExpression) + .Where(e => state.SemanticModel.GetSymbolInfo(e, cancellationToken).Symbol?.OriginalDefinition == methodSymbol); + + foreach (var node in invocations) + processResult(CreateFinderLocation(node, state, cancellationToken), processResultData); + foreach (var node in nodes) { if (!syntaxFacts.IsAnonymousFunctionExpression(node)) @@ -103,14 +110,17 @@ protected override async ValueTask> FindReference } if (convertedType == methodSymbol.ContainingType) - convertedAnonymousFunctions.Add(node); + { + var finderLocation = CreateFinderLocation(node, state, cancellationToken); + processResult(finderLocation, processResultData); + } } - var invocations = nodes.Where(syntaxFacts.IsInvocationExpression) - .Where(e => state.SemanticModel.GetSymbolInfo(e, cancellationToken).Symbol?.OriginalDefinition == methodSymbol); + return; - return invocations.Concat(convertedAnonymousFunctions).SelectAsArray( - node => new FinderLocation( + static FinderLocation CreateFinderLocation(SyntaxNode node, FindReferencesDocumentState state, CancellationToken cancellationToken) + { + return new FinderLocation( node, new ReferenceLocation( state.Document, @@ -119,6 +129,7 @@ protected override async ValueTask> FindReference isImplicit: false, GetSymbolUsageInfo(node, state, cancellationToken), GetAdditionalFindUsagesProperties(node, state), - CandidateReason.None))); + CandidateReason.None)); + } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 25f61c8deda53..eb3a7e932959e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -203,7 +203,7 @@ private async Task ProcessProjectAsync(Project project, ImmutableArray { await finder.DetermineDocumentsToSearchAsync( symbol, globalAliases, project, _documents, - static (doc, documents) => documents.Add(doc), + StandardCallbacks.AddToHashSet, foundDocuments, _options, cancellationToken).ConfigureAwait(false); @@ -272,12 +272,15 @@ private async Task ProcessDocumentAsync( // just grab those once here and hold onto them for the lifetime of this call. var cache = await FindReferenceCache.GetCacheAsync(document, cancellationToken).ConfigureAwait(false); + // scratch array to place results in. Populated/inspected/cleared in inner loop. + using var _ = ArrayBuilder.GetInstance(out var foundReferenceLocations); + foreach (var symbol in symbols) { var globalAliases = TryGet(symbolToGlobalAliases, symbol); var state = new FindReferencesDocumentState(cache, globalAliases); - await ProcessDocumentAsync(symbol, state).ConfigureAwait(false); + await ProcessDocumentAsync(symbol, state, foundReferenceLocations).ConfigureAwait(false); } } finally @@ -286,7 +289,7 @@ private async Task ProcessDocumentAsync( } async Task ProcessDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state) + ISymbol symbol, FindReferencesDocumentState state, ArrayBuilder foundReferenceLocations) { cancellationToken.ThrowIfCancellationRequested(); @@ -297,10 +300,12 @@ async Task ProcessDocumentAsync( var group = _symbolToGroup[symbol]; foreach (var finder in _finders) { - var references = await finder.FindReferencesInDocumentAsync( - symbol, state, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in references) + await finder.FindReferencesInDocumentAsync( + symbol, state, StandardCallbacks.AddToArrayBuilder, foundReferenceLocations, _options, cancellationToken).ConfigureAwait(false); + foreach (var (_, location) in foundReferenceLocations) await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); + + foundReferenceLocations.Clear(); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs index 0e982257b278e..cb31339e8022c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_FindReferencesInDocuments.cs @@ -101,16 +101,21 @@ async ValueTask PerformSearchInDocumentAsync( async ValueTask PerformSearchInDocumentWorkerAsync( ISymbol symbol, FindReferencesDocumentState state) { + // Scratch buffer to place references for each finder. Cleared at the end of every loop iteration. + using var _ = ArrayBuilder.GetInstance(out var referencesForFinder); + // Always perform a normal search, looking for direct references to exactly that symbol. foreach (var finder in _finders) { - var references = await finder.FindReferencesInDocumentAsync( - symbol, state, _options, cancellationToken).ConfigureAwait(false); - foreach (var (_, location) in references) + await finder.FindReferencesInDocumentAsync( + symbol, state, StandardCallbacks.AddToArrayBuilder, referencesForFinder, _options, cancellationToken).ConfigureAwait(false); + foreach (var (_, location) in referencesForFinder) { var group = await ReportGroupAsync(symbol, cancellationToken).ConfigureAwait(false); await _progress.OnReferenceFoundAsync(group, symbol, location, cancellationToken).ConfigureAwait(false); } + + referencesForFinder.Clear(); } // Now, for symbols that could involve inheritance, look for references to the same named entity, and diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs index 5e70cfac3b188..574d7b6652cd9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMemberScopedReferenceFinder.cs @@ -49,23 +49,24 @@ protected sealed override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( TSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { var container = GetContainer(symbol); if (container != null) - return await FindReferencesInContainerAsync(symbol, container, state, cancellationToken).ConfigureAwait(false); - - if (symbol.ContainingType != null && symbol.ContainingType.IsScriptClass) + { + await FindReferencesInContainerAsync(symbol, container, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + } + else if (symbol.ContainingType != null && symbol.ContainingType.IsScriptClass) { var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); - return await FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken).ConfigureAwait(false); + await FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - - return []; } private static ISymbol? GetContainer(ISymbol symbol) @@ -96,10 +97,12 @@ protected sealed override async ValueTask> FindRe return null; } - private ValueTask> FindReferencesInContainerAsync( + private ValueTask FindReferencesInContainerAsync( TSymbol symbol, ISymbol container, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var service = state.Document.GetRequiredLanguageService(); @@ -118,6 +121,6 @@ private ValueTask> FindReferencesInContainerAsync } } - return FindReferencesInTokensAsync(symbol, state, tokens.ToImmutable(), cancellationToken); + return FindReferencesInTokensAsync(symbol, state, tokens.ToImmutable(), processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs index 094c5db7de444..e9944e7ff30c0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractMethodOrPropertyOrEventSymbolReferenceFinder.cs @@ -4,10 +4,8 @@ using System.Collections.Immutable; using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index c2a586f94d1e3..39b19e605551a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -24,9 +24,6 @@ internal abstract partial class AbstractReferenceFinder : IReferenceFinder public const string ContainingTypeInfoPropertyName = "ContainingTypeInfo"; public const string ContainingMemberInfoPropertyName = "ContainingMemberInfo"; - protected static readonly Action> StandardHashSetAddCallback = - static (doc, set) => set.Add(doc); - public abstract Task> DetermineGlobalAliasesAsync( ISymbol symbol, Project project, CancellationToken cancellationToken); @@ -36,8 +33,8 @@ public abstract ValueTask> DetermineCascadedSymbolsAsync public abstract Task DetermineDocumentsToSearchAsync( ISymbol symbol, HashSet? globalAliases, Project project, IImmutableSet? documents, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); - public abstract ValueTask> FindReferencesInDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken); + public abstract ValueTask FindReferencesInDocumentAsync( + ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); private static ValueTask<(bool matched, CandidateReason reason)> SymbolsMatchAsync( ISymbol symbol, FindReferencesDocumentState state, SyntaxToken token, CancellationToken cancellationToken) @@ -166,29 +163,32 @@ protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string n => syntaxFacts.IsIdentifier(token) && syntaxFacts.TextMatch(token.ValueText, name); [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - protected static async ValueTask> FindReferencesInDocumentUsingIdentifierAsync( + protected static async ValueTask FindReferencesInDocumentUsingIdentifierAsync( ISymbol symbol, string identifier, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var tokens = await FindMatchingIdentifierTokensAsync(state, identifier, cancellationToken).ConfigureAwait(false); - return await FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken).ConfigureAwait(false); + await FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); } public static ValueTask> FindMatchingIdentifierTokensAsync(FindReferencesDocumentState state, string identifier, CancellationToken cancellationToken) => state.Cache.FindMatchingIdentifierTokensAsync(state.Document, identifier, cancellationToken); - protected static async ValueTask> FindReferencesInTokensAsync( + protected static async ValueTask FindReferencesInTokensAsync( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray tokens, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (tokens.IsEmpty) - return []; + return; - using var _ = ArrayBuilder.GetInstance(out var locations); foreach (var token in tokens) { cancellationToken.ThrowIfCancellationRequested(); @@ -199,11 +199,9 @@ protected static async ValueTask> FindReferencesI { var finderLocation = CreateFinderLocation(state, token, reason, cancellationToken); - locations.Add(finderLocation); + processResult(finderLocation, processResultData); } } - - return locations.ToImmutableAndClear(); } protected static FinderLocation CreateFinderLocation(FindReferencesDocumentState state, SyntaxToken token, CandidateReason reason, CancellationToken cancellationToken) @@ -245,27 +243,29 @@ public static ReferenceLocation CreateReferenceLocation(FindReferencesDocumentSt return null; } - protected static async Task> FindLocalAliasReferencesAsync( + protected static async Task FindLocalAliasReferencesAsync( ArrayBuilder initialReferences, ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); - return aliasSymbols.IsDefaultOrEmpty - ? [] - : await FindReferencesThroughLocalAliasSymbolsAsync(symbol, state, aliasSymbols, cancellationToken).ConfigureAwait(false); + if (!aliasSymbols.IsDefaultOrEmpty) + await FindReferencesThroughLocalAliasSymbolsAsync(symbol, state, aliasSymbols, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected static async Task> FindLocalAliasReferencesAsync( + protected static async Task FindLocalAliasReferencesAsync( ArrayBuilder initialReferences, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var aliasSymbols = GetLocalAliasSymbols(state, initialReferences, cancellationToken); - return aliasSymbols.IsDefaultOrEmpty - ? [] - : await FindReferencesThroughLocalAliasSymbolsAsync(state, aliasSymbols, cancellationToken).ConfigureAwait(false); + if (!aliasSymbols.IsDefaultOrEmpty) + await FindReferencesThroughLocalAliasSymbolsAsync(state, aliasSymbols, processResult, processResultData, cancellationToken).ConfigureAwait(false); } private static ImmutableArray GetLocalAliasSymbols( @@ -284,53 +284,49 @@ private static ImmutableArray GetLocalAliasSymbols( return aliasSymbols.ToImmutableAndClear(); } - private static async Task> FindReferencesThroughLocalAliasSymbolsAsync( + private static async Task FindReferencesThroughLocalAliasSymbolsAsync( ISymbol symbol, FindReferencesDocumentState state, ImmutableArray localAliasSymbols, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var allAliasReferences); foreach (var localAliasSymbol in localAliasSymbols) { - var aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - symbol, localAliasSymbol.Name, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + await FindReferencesInDocumentUsingIdentifierAsync( + symbol, localAliasSymbol.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(localAliasSymbol.Name, state.SyntaxFacts, out var simpleName)) { - aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - symbol, simpleName, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + await FindReferencesInDocumentUsingIdentifierAsync( + symbol, simpleName, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } - - return allAliasReferences.ToImmutableAndClear(); } - private static async Task> FindReferencesThroughLocalAliasSymbolsAsync( + private static async Task FindReferencesThroughLocalAliasSymbolsAsync( FindReferencesDocumentState state, ImmutableArray localAliasSymbols, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var allAliasReferences); foreach (var aliasSymbol in localAliasSymbols) { - var aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, aliasSymbol.Name, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + await FindReferencesInDocumentUsingIdentifierAsync( + aliasSymbol, aliasSymbol.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + // the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the // shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {}) if (TryGetNameWithoutAttributeSuffix(aliasSymbol.Name, state.SyntaxFacts, out var simpleName)) { - aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync( - aliasSymbol, simpleName, state, cancellationToken).ConfigureAwait(false); - allAliasReferences.AddRange(aliasReferences); + await FindReferencesInDocumentUsingIdentifierAsync( + aliasSymbol, simpleName, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } - - return allAliasReferences.ToImmutableAndClear(); } protected static Task FindDocumentsWithPredicateAsync( @@ -372,45 +368,43 @@ protected static Task FindDocumentsWithForEachStatementsAsync(Project pro /// /// If the `node` implicitly matches the `symbol`, then it will be added to `locations`. /// - protected delegate void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations); + protected delegate void CollectMatchingReferences( + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData); - protected static async Task> FindReferencesInDocumentAsync( + protected static async Task FindReferencesInDocumentAsync( FindReferencesDocumentState state, Func isRelevantDocument, - CollectMatchingReferences collectMatchingReferences, + CollectMatchingReferences collectMatchingReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var document = state.Document; var syntaxTreeInfo = await SyntaxTreeIndex.GetRequiredIndexAsync(document, cancellationToken).ConfigureAwait(false); if (isRelevantDocument(syntaxTreeInfo)) { - using var _ = ArrayBuilder.GetInstance(out var locations); - foreach (var node in state.Root.DescendantNodesAndSelf()) { cancellationToken.ThrowIfCancellationRequested(); - collectMatchingReferences(node, state, locations); + collectMatchingReferences(node, state, processResult, processResultData); } - - return locations.ToImmutableAndClear(); } - - return []; } - protected Task> FindReferencesInForEachStatementsAsync( + protected Task FindReferencesInForEachStatementsAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsForEachStatement; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var info = state.SemanticFacts.GetForEachSymbols(state.SemanticModel, node); @@ -422,30 +416,33 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location: location, isImplicit: true, symbolUsageInfo, GetAdditionalFindUsagesProperties(node, state), - candidateReason: CandidateReason.None))); + candidateReason: CandidateReason.None)); + processResult(result, processResultData); } } } - protected Task> FindReferencesInCollectionInitializerAsync( + protected Task FindReferencesInCollectionInitializerAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsCollectionInitializer; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { if (!state.SyntaxFacts.IsObjectCollectionInitializer(node)) return; @@ -460,31 +457,34 @@ void CollectMatchingReferences( var location = expression.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(expression, state, cancellationToken); - locations.Add(new FinderLocation(expression, new ReferenceLocation( + var result = new FinderLocation(expression, new ReferenceLocation( state.Document, alias: null, location: location, isImplicit: true, symbolUsageInfo, GetAdditionalFindUsagesProperties(expression, state), - candidateReason: CandidateReason.None))); + candidateReason: CandidateReason.None)); + processResult(result, processResultData); } } } } - protected Task> FindReferencesInDeconstructionAsync( + protected Task FindReferencesInDeconstructionAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsDeconstruction; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var semanticModel = state.SemanticModel; var semanticFacts = state.SemanticFacts; @@ -500,25 +500,28 @@ void CollectMatchingReferences( var location = state.SyntaxFacts.GetDeconstructionReferenceLocation(node); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } - protected Task> FindReferencesInAwaitExpressionAsync( + protected Task FindReferencesInAwaitExpressionAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsAwait; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var awaitExpressionMethod = state.SemanticFacts.GetGetAwaiterMethod(state.SemanticModel, node); @@ -527,25 +530,28 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } - protected Task> FindReferencesInImplicitObjectCreationExpressionAsync( + protected Task FindReferencesInImplicitObjectCreationExpressionAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { // Avoid binding unrelated nodes if (!state.SyntaxFacts.IsImplicitObjectCreationExpression(node)) @@ -558,9 +564,10 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } @@ -841,8 +848,10 @@ protected abstract Task DetermineDocumentsToSearchAsync( Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); - protected abstract ValueTask> FindReferencesInDocumentAsync( - TSymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken); + protected abstract ValueTask FindReferencesInDocumentAsync( + TSymbol symbol, FindReferencesDocumentState state, + Action processResult, TData processResultData, + FindReferencesSearchOptions options, CancellationToken cancellationToken); protected virtual Task> DetermineGlobalAliasesAsync( TSymbol symbol, Project project, CancellationToken cancellationToken) @@ -869,12 +878,12 @@ public sealed override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - public sealed override ValueTask> FindReferencesInDocumentAsync( - ISymbol symbol, FindReferencesDocumentState state, FindReferencesSearchOptions options, CancellationToken cancellationToken) + public sealed override ValueTask FindReferencesInDocumentAsync( + ISymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { return symbol is TSymbol typedSymbol && CanFind(typedSymbol) - ? FindReferencesInDocumentAsync(typedSymbol, state, options, cancellationToken) - : new ValueTask>([]); + ? FindReferencesInDocumentAsync(typedSymbol, state, processResult, processResultData, options, cancellationToken) + : ValueTaskFactory.CompletedTask; } public sealed override ValueTask> DetermineCascadedSymbolsAsync( @@ -896,11 +905,11 @@ protected virtual ValueTask> DetermineCascadedSymbolsAsy return new([]); } - protected static ValueTask> FindReferencesInDocumentUsingSymbolNameAsync( - TSymbol symbol, FindReferencesDocumentState state, CancellationToken cancellationToken) + protected static ValueTask FindReferencesInDocumentUsingSymbolNameAsync( + TSymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { return FindReferencesInDocumentUsingIdentifierAsync( - symbol, symbol.Name, state, cancellationToken); + symbol, symbol.Name, state, processResult, processResultData, cancellationToken); } protected static async Task> GetAllMatchingGlobalAliasNamesAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs index d3c9275698895..13e1de1396ca0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder_GlobalSuppressions.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -51,28 +50,30 @@ static bool SupportsGlobalSuppression(ISymbol symbol) /// [assembly: SuppressMessage("RuleCategory", "RuleId', Scope = "member", Target = "~F:C.Field")] /// [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - protected static async ValueTask> FindReferencesInDocumentInsideGlobalSuppressionsAsync( + protected static async ValueTask FindReferencesInDocumentInsideGlobalSuppressionsAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { if (!ShouldFindReferencesInGlobalSuppressions(symbol, out var docCommentId)) - return []; + return; // Check if we have any relevant global attributes in this document. var info = await SyntaxTreeIndex.GetRequiredIndexAsync(state.Document, cancellationToken).ConfigureAwait(false); if (!info.ContainsGlobalSuppressMessageAttribute) - return []; + return; var semanticModel = state.SemanticModel; var suppressMessageAttribute = semanticModel.Compilation.SuppressMessageAttributeType(); if (suppressMessageAttribute == null) - return []; + return; // Check if we have any instances of the symbol documentation comment ID string literals within global attributes. // These string literals represent references to the symbol. if (!TryGetExpectedDocumentationCommentId(docCommentId, out var expectedDocCommentId)) - return []; + return; var syntaxFacts = state.SyntaxFacts; @@ -80,17 +81,16 @@ protected static async ValueTask> FindReferencesI // perform semantic checks to ensure these are valid references to the symbol // and if so, add these locations to the computed references. var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - using var _ = ArrayBuilder.GetInstance(out var locations); foreach (var token in root.DescendantTokens()) { if (IsCandidate(state, token, expectedDocCommentId.Span, suppressMessageAttribute, cancellationToken, out var offsetOfReferenceInToken)) { var referenceLocation = CreateReferenceLocation(offsetOfReferenceInToken, token, root, state.Document, syntaxFacts); - locations.Add(new FinderLocation(token.GetRequiredParent(), referenceLocation)); + processResult(new FinderLocation(token.GetRequiredParent(), referenceLocation), processResultData); } } - return locations.ToImmutableAndClear(); + return; // Local functions static bool IsCandidate( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs index 756ce8036d1fa..38cc211cfb9ad 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractTypeParameterSymbolReferenceFinder.cs @@ -7,16 +7,17 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; internal abstract class AbstractTypeParameterSymbolReferenceFinder : AbstractReferenceFinder { - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( ITypeParameterSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -30,15 +31,19 @@ protected sealed override async ValueTask> FindRe var tokens = await FindMatchingIdentifierTokensAsync(state, symbol.Name, cancellationToken).ConfigureAwait(false); - var normalReferences = await FindReferencesInTokensAsync( + await FindReferencesInTokensAsync( symbol, state, tokens.WhereAsArray(static (token, state) => !IsObjectCreationToken(token, state), state), + processResult, + processResultData, cancellationToken).ConfigureAwait(false); - var objectCreationReferences = GetObjectCreationReferences( - tokens.WhereAsArray(static (token, state) => IsObjectCreationToken(token, state), state)); + GetObjectCreationReferences( + tokens.WhereAsArray(static (token, state) => IsObjectCreationToken(token, state), state), + processResult, + processResultData); - return normalReferences.Concat(objectCreationReferences); + return; static bool IsObjectCreationToken(SyntaxToken token, FindReferencesDocumentState state) { @@ -47,19 +52,18 @@ static bool IsObjectCreationToken(SyntaxToken token, FindReferencesDocumentState syntaxFacts.IsObjectCreationExpression(token.Parent.Parent); } - ImmutableArray GetObjectCreationReferences(ImmutableArray objectCreationTokens) + void GetObjectCreationReferences( + ImmutableArray objectCreationTokens, + Action processResult, + TData processResultData) { - using var _ = ArrayBuilder.GetInstance(out var result); - foreach (var token in objectCreationTokens) { Contract.ThrowIfNull(token.Parent?.Parent); var typeInfo = state.SemanticModel.GetTypeInfo(token.Parent.Parent, cancellationToken); if (symbol.Equals(typeInfo.Type, SymbolEqualityComparer.Default)) - result.Add(CreateFinderLocation(state, token, CandidateReason.None, cancellationToken)); + processResult(CreateFinderLocation(state, token, CandidateReason.None, cancellationToken), processResultData); } - - return result.ToImmutableAndClear(); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index c79b50715238d..4258a0eca666b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -51,9 +51,11 @@ protected override Task DetermineDocumentsToSearchAsync( }, symbol.ContainingType.Name, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -68,7 +70,9 @@ protected sealed override async ValueTask> FindRe static (token, tuple) => TokensMatch(tuple.state, token, tuple.methodSymbol.ContainingType.Name, tuple.cancellationToken), (state, methodSymbol, cancellationToken)); - return await FindReferencesInTokensAsync(methodSymbol, state, totalTokens, cancellationToken).ConfigureAwait(false); + await FindReferencesInTokensAsync(methodSymbol, state, totalTokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + + return; // local functions static bool TokensMatch( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 8fea80d2ffb77..5037d55e9d1ad 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -9,8 +9,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -90,18 +90,18 @@ private static bool IsPotentialReference(PredefinedType predefinedType, ISyntaxF => syntaxFacts.TryGetPredefinedType(token, out var actualType) && predefinedType == actualType; - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _1 = ArrayBuilder.GetInstance(out var result); - // First just look for this normal constructor references using the name of it's containing type. var name = methodSymbol.ContainingType.Name; await AddReferencesInDocumentWorkerAsync( - methodSymbol, name, state, result, cancellationToken).ConfigureAwait(false); + methodSymbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); // Next, look for constructor references through a global alias to our containing type. foreach (var globalAlias in state.GlobalAliases) @@ -113,67 +113,61 @@ await AddReferencesInDocumentWorkerAsync( continue; await AddReferencesInDocumentWorkerAsync( - methodSymbol, globalAlias, state, result, cancellationToken).ConfigureAwait(false); + methodSymbol, globalAlias, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - // Nest, our containing type might itself have local aliases to it in this particular file. - // If so, see what the local aliases are and then search for constructor references to that. - using var _2 = ArrayBuilder.GetInstance(out var typeReferences); - await NamedTypeSymbolReferenceFinder.AddReferencesToTypeOrGlobalAliasToItAsync( - methodSymbol.ContainingType, state, typeReferences, cancellationToken).ConfigureAwait(false); - - var aliasReferences = await FindLocalAliasReferencesAsync( - typeReferences, methodSymbol, state, cancellationToken).ConfigureAwait(false); - // Finally, look for constructor references to predefined types (like `new int()`), // implicit object references, and inside global suppression attributes. - result.AddRange(await FindPredefinedTypeReferencesAsync( - methodSymbol, state, cancellationToken).ConfigureAwait(false)); - - result.AddRange(await FindReferencesInImplicitObjectCreationExpressionAsync( - methodSymbol, state, cancellationToken).ConfigureAwait(false)); + await FindPredefinedTypeReferencesAsync( + methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - result.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - methodSymbol, state, cancellationToken).ConfigureAwait(false)); + await FindReferencesInImplicitObjectCreationExpressionAsync( + methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return result.ToImmutableAndClear(); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + methodSymbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } /// /// Finds references to in this , but only if it referenced /// though (which might be the actual name of the type, or a global alias to it). /// - private static async Task AddReferencesInDocumentWorkerAsync( + private static async Task AddReferencesInDocumentWorkerAsync( IMethodSymbol symbol, string name, FindReferencesDocumentState state, - ArrayBuilder result, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - result.AddRange(await FindOrdinaryReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); - result.AddRange(await FindAttributeReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); + await FindOrdinaryReferencesAsync( + symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + await FindAttributeReferencesAsync( + symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static ValueTask> FindOrdinaryReferencesAsync( + private static ValueTask FindOrdinaryReferencesAsync( IMethodSymbol symbol, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return FindReferencesInDocumentUsingIdentifierAsync( - symbol, name, state, cancellationToken); + symbol, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask> FindPredefinedTypeReferencesAsync( + private static ValueTask FindPredefinedTypeReferencesAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var predefinedType = symbol.ContainingType.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return new([]); + return ValueTaskFactory.CompletedTask; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -181,23 +175,27 @@ private static ValueTask> FindPredefinedTypeRefer static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken); + return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask> FindAttributeReferencesAsync( + private static ValueTask FindAttributeReferencesAsync( IMethodSymbol symbol, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var simpleName) - ? FindReferencesInDocumentUsingIdentifierAsync(symbol, simpleName, state, cancellationToken) - : new([]); + ? FindReferencesInDocumentUsingIdentifierAsync(symbol, simpleName, state, processResult, processResultData, cancellationToken) + : ValueTaskFactory.CompletedTask; } - private Task> FindReferencesInImplicitObjectCreationExpressionAsync( + private Task FindReferencesInImplicitObjectCreationExpressionAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { // Only check `new (...)` calls that supply enough arguments to match all the required parameters for the constructor. @@ -210,13 +208,13 @@ private Task> FindReferencesInImplicitObjectCreat ? -1 : symbol.Parameters.Length; - return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, cancellationToken); + return FindReferencesInDocumentAsync(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) => syntaxTreeInfo.ContainsImplicitObjectCreation; void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, ArrayBuilder locations) + SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) { var syntaxFacts = state.SyntaxFacts; if (!syntaxFacts.IsImplicitObjectCreationExpression(node)) @@ -237,9 +235,10 @@ void CollectMatchingReferences( var location = node.GetFirstToken().GetLocation(); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, new ReferenceLocation( + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, - GetAdditionalFindUsagesProperties(node, state), CandidateReason.None))); + GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); + processResult(result, processResultData); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs index 245f390365b46..a6c1623d5ba95 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/DestructorSymbolReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -28,12 +29,14 @@ protected override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected override ValueTask> FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IMethodSymbol methodSymbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return new ValueTask>([]); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs index 76a169a867822..8784ccd1b37ef 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/EventSymbolReferenceFinder.cs @@ -48,12 +48,14 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected sealed override ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IEventSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingSymbolNameAsync(symbol, state, cancellationToken); + return FindReferencesInDocumentUsingSymbolNameAsync(symbol, state, processResult, processResultData, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs index b4e00a0482da6..4078ddcbba83d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitConversionSymbolReferenceFinder.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; @@ -48,8 +47,8 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( Contract.ThrowIfNull(underlyingNamedType); using var _ = PooledHashSet.GetInstance(out var result); - await FindDocumentsAsync(project, documents, StandardHashSetAddCallback, result, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); - await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), StandardHashSetAddCallback, result, cancellationToken).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, StandardCallbacks.AddToHashSet, result, cancellationToken, underlyingNamedType.Name).ConfigureAwait(false); + await FindDocumentsAsync(project, documents, underlyingNamedType.SpecialType.ToPredefinedType(), StandardCallbacks.AddToHashSet, result, cancellationToken).ConfigureAwait(false); // Ignore any documents that don't also have an explicit cast in them. foreach (var document in result) @@ -60,9 +59,11 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( } } - protected sealed override ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -72,7 +73,7 @@ protected sealed override ValueTask> FindReferenc static (token, state) => IsPotentialReference(state.SyntaxFacts, token), state); - return FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken); + return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs index b048e24c1a03f..a92e4f0ca1eba 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ExplicitInterfaceMethodReferenceFinder.cs @@ -7,6 +7,7 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -29,13 +30,15 @@ protected sealed override Task DetermineDocumentsToSearchAsync( return Task.CompletedTask; } - protected sealed override ValueTask> FindReferencesInDocumentAsync( + protected sealed override ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // An explicit method can't be referenced anywhere. - return new([]); + return ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs index d3657581f7cfd..11c23a50ec558 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/FieldSymbolReferenceFinder.cs @@ -40,16 +40,17 @@ protected override async Task DetermineDocumentsToSearchAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( IFieldSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var nameReferences = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - return nameReferences.Concat(suppressionReferences); + await FindReferencesInDocumentUsingSymbolNameAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs index 8e4738b2a4728..f5b717542dc20 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/IReferenceFinder.cs @@ -63,9 +63,11 @@ Task DetermineDocumentsToSearchAsync( /// /// Implementations of this method must be thread-safe. /// - ValueTask> FindReferencesInDocumentAsync( + ValueTask FindReferencesInDocumentAsync( ISymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs index ff8f1084f9b13..35816840c7d6c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamedTypeSymbolReferenceFinder.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols.Finders; @@ -104,9 +105,11 @@ private static bool IsPotentialReference( predefinedType == actualType; } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( INamedTypeSymbol namedType, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -115,31 +118,34 @@ protected override async ValueTask> FindReference // First find all references to this type, either with it's actual name, or through potential // global alises to it. await AddReferencesToTypeOrGlobalAliasToItAsync( - namedType, state, initialReferences, cancellationToken).ConfigureAwait(false); + namedType, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); + + // The items in initialReferences need to be both reported and used later to calculate additional results. + foreach (var location in initialReferences) + processResult(location, processResultData); // This named type may end up being locally aliased as well. If so, now find all the references // to the local alias. - initialReferences.AddRange(await FindLocalAliasReferencesAsync( - initialReferences, state, cancellationToken).ConfigureAwait(false)); + await FindLocalAliasReferencesAsync( + initialReferences, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - initialReferences.AddRange(await FindPredefinedTypeReferencesAsync( - namedType, state, cancellationToken).ConfigureAwait(false)); + await FindPredefinedTypeReferencesAsync( + namedType, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - initialReferences.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - namedType, state, cancellationToken).ConfigureAwait(false)); - - return initialReferences.ToImmutableAndClear(); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + namedType, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - internal static async ValueTask AddReferencesToTypeOrGlobalAliasToItAsync( + internal static async ValueTask AddReferencesToTypeOrGlobalAliasToItAsync( INamedTypeSymbol namedType, FindReferencesDocumentState state, - ArrayBuilder nonAliasReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { await AddNonAliasReferencesAsync( - namedType, namedType.Name, state, nonAliasReferences, cancellationToken).ConfigureAwait(false); + namedType, namedType.Name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); foreach (var globalAlias in state.GlobalAliases) { @@ -150,7 +156,7 @@ await AddNonAliasReferencesAsync( continue; await AddNonAliasReferencesAsync( - namedType, globalAlias, state, nonAliasReferences, cancellationToken).ConfigureAwait(false); + namedType, globalAlias, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } @@ -159,24 +165,27 @@ await AddNonAliasReferencesAsync( /// only if it referenced though (which might be the actual name /// of the type, or a global alias to it). /// - private static async ValueTask AddNonAliasReferencesAsync( + private static async ValueTask AddNonAliasReferencesAsync( INamedTypeSymbol symbol, string name, FindReferencesDocumentState state, - ArrayBuilder nonAliasesReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { - nonAliasesReferences.AddRange(await FindOrdinaryReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); + await FindOrdinaryReferencesAsync( + symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - nonAliasesReferences.AddRange(await FindAttributeReferencesAsync( - symbol, name, state, cancellationToken).ConfigureAwait(false)); + await FindAttributeReferencesAsync( + symbol, name, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static ValueTask> FindOrdinaryReferencesAsync( + private static ValueTask FindOrdinaryReferencesAsync( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { // Get the parent node that best matches what this token represents. For example, if we have `new a.b()` @@ -185,17 +194,19 @@ private static ValueTask> FindOrdinaryReferencesA // associate with the type, but rather with the constructor itself. return FindReferencesInDocumentUsingIdentifierAsync( - namedType, name, state, cancellationToken); + namedType, name, state, processResult, processResultData, cancellationToken); } - private static ValueTask> FindPredefinedTypeReferencesAsync( + private static ValueTask FindPredefinedTypeReferencesAsync( INamedTypeSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var predefinedType = symbol.SpecialType.ToPredefinedType(); if (predefinedType == PredefinedType.None) - return new([]); + return ValueTaskFactory.CompletedTask; var tokens = state.Root .DescendantTokens(descendIntoTrivia: true) @@ -203,17 +214,19 @@ private static ValueTask> FindPredefinedTypeRefer static (token, tuple) => IsPotentialReference(tuple.predefinedType, tuple.state.SyntaxFacts, token), (state, predefinedType)); - return FindReferencesInTokensAsync(symbol, state, tokens, cancellationToken); + return FindReferencesInTokensAsync(symbol, state, tokens, processResult, processResultData, cancellationToken); } - private static ValueTask> FindAttributeReferencesAsync( + private static ValueTask FindAttributeReferencesAsync( INamedTypeSymbol namedType, string name, FindReferencesDocumentState state, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { return TryGetNameWithoutAttributeSuffix(name, state.SyntaxFacts, out var nameWithoutSuffix) - ? FindReferencesInDocumentUsingIdentifierAsync(namedType, nameWithoutSuffix, state, cancellationToken) - : new([]); + ? FindReferencesInDocumentUsingIdentifierAsync(namedType, nameWithoutSuffix, state, processResult, processResultData, cancellationToken) + : ValueTaskFactory.CompletedTask; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs index bb03240ba2ce4..e08a522c68576 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/NamespaceSymbolReferenceFinder.cs @@ -50,24 +50,26 @@ await FindDocumentsAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( INamespaceSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var initialReferences); - if (symbol.IsGlobalNamespace) { await AddGlobalNamespaceReferencesAsync( - symbol, state, initialReferences, cancellationToken).ConfigureAwait(false); + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } else { + using var _ = ArrayBuilder.GetInstance(out var initialReferences); + var namespaceName = symbol.Name; await AddNamedReferencesAsync( - symbol, namespaceName, state, initialReferences, cancellationToken).ConfigureAwait(false); + symbol, namespaceName, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); foreach (var globalAlias in state.GlobalAliases) { @@ -78,41 +80,45 @@ await AddNamedReferencesAsync( continue; await AddNamedReferencesAsync( - symbol, globalAlias, state, initialReferences, cancellationToken).ConfigureAwait(false); + symbol, globalAlias, state, StandardCallbacks.AddToArrayBuilder, initialReferences, cancellationToken).ConfigureAwait(false); } - initialReferences.AddRange(await FindLocalAliasReferencesAsync( - initialReferences, symbol, state, cancellationToken).ConfigureAwait(false)); + // The items in initialReferences need to be both reported and used later to calculate additional results. + foreach (var location in initialReferences) + processResult(location, processResultData); - initialReferences.AddRange(await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false)); - } + await FindLocalAliasReferencesAsync( + initialReferences, symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return initialReferences.ToImmutableAndClear(); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); + } } /// /// Finds references to in this , but only if it referenced /// though (which might be the actual name of the type, or a global alias to it). /// - private static async ValueTask AddNamedReferencesAsync( + private static async ValueTask AddNamedReferencesAsync( INamespaceSymbol symbol, string name, FindReferencesDocumentState state, - ArrayBuilder initialReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var tokens = await FindMatchingIdentifierTokensAsync( state, name, cancellationToken).ConfigureAwait(false); - initialReferences.AddRange(await FindReferencesInTokensAsync( - symbol, state, tokens, cancellationToken).ConfigureAwait(false)); + await FindReferencesInTokensAsync( + symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - private static async Task AddGlobalNamespaceReferencesAsync( + private static async Task AddGlobalNamespaceReferencesAsync( INamespaceSymbol symbol, FindReferencesDocumentState state, - ArrayBuilder initialReferences, + Action processResult, + TData processResultData, CancellationToken cancellationToken) { var tokens = state.Root @@ -121,7 +127,7 @@ private static async Task AddGlobalNamespaceReferencesAsync( static (token, state) => state.SyntaxFacts.IsGlobalNamespaceKeyword(token), state); - initialReferences.AddRange(await FindReferencesInTokensAsync( - symbol, state, tokens, cancellationToken).ConfigureAwait(false)); + await FindReferencesInTokensAsync( + symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs index bd1d4210e5476..5b3b92945dc16 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OperatorSymbolReferenceFinder.cs @@ -47,9 +47,11 @@ private static Task FindDocumentsAsync( project, documents, static (index, op) => index.ContainsPredefinedOperator(op), op, processResult, processResultData, cancellationToken); } - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -60,12 +62,10 @@ protected sealed override async ValueTask> FindRe static (token, tuple) => IsPotentialReference(tuple.state.SyntaxFacts, tuple.op, token), (state, op)); - var opReferences = await FindReferencesInTokensAsync( - symbol, state, tokens, cancellationToken).ConfigureAwait(false); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - - return opReferences.Concat(suppressionReferences); + await FindReferencesInTokensAsync( + symbol, state, tokens, processResult, processResultData, cancellationToken).ConfigureAwait(false); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } private static bool IsPotentialReference( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs index 7ace214c57e6e..1131b5489010d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/OrdinaryMethodReferenceFinder.cs @@ -108,34 +108,30 @@ private static bool IsGetAwaiterMethod(IMethodSymbol methodSymbol) private static bool IsAddMethod(IMethodSymbol methodSymbol) => methodSymbol.Name == WellKnownMemberNames.CollectionInitializerAddMethodName; - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var nameMatches = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - - var forEachMatches = IsForEachMethod(symbol) - ? await FindReferencesInForEachStatementsAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + await FindReferencesInDocumentUsingSymbolNameAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var deconstructMatches = IsDeconstructMethod(symbol) - ? await FindReferencesInDeconstructionAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachMethod(symbol)) + await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var getAwaiterMatches = IsGetAwaiterMethod(symbol) - ? await FindReferencesInAwaitExpressionAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (IsDeconstructMethod(symbol)) + await FindReferencesInDeconstructionAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); + if (IsGetAwaiterMethod(symbol)) + await FindReferencesInAwaitExpressionAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var addMatches = IsAddMethod(symbol) - ? await FindReferencesInCollectionInitializerAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - return nameMatches.Concat(forEachMatches, deconstructMatches, getAwaiterMatches, suppressionReferences, addMatches); + if (IsAddMethod(symbol)) + await FindReferencesInCollectionInitializerAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs index d7231cad5721a..b13c380c6f9c9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ParameterSymbolReferenceFinder.cs @@ -38,13 +38,15 @@ protected override Task DetermineDocumentsToSearchAsync( return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name); } - protected override ValueTask> FindReferencesInDocumentAsync( + protected override ValueTask FindReferencesInDocumentAsync( IParameterSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return FindReferencesInDocumentUsingIdentifierAsync(symbol, symbol.Name, state, cancellationToken); + return FindReferencesInDocumentUsingIdentifierAsync(symbol, symbol.Name, state, processResult, processResultData, cancellationToken); } protected override async ValueTask> DetermineCascadedSymbolsAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index a15f25ab2979c..7c12a31b19e68 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -60,34 +60,35 @@ await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( await FindDocumentsWithGlobalSuppressMessageAttributeAsync(project, documents, processResult, processResultData, cancellationToken).ConfigureAwait(false); } - protected override async ValueTask> FindReferencesInDocumentAsync( + protected override async ValueTask FindReferencesInDocumentAsync( IMethodSymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var references = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); + await FindReferencesInDocumentUsingSymbolNameAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); if (symbol.AssociatedSymbol is not IPropertySymbol property || !options.AssociatePropertyReferencesWithSpecificAccessor) { - return references; + return; } - var propertyReferences = await ReferenceFinders.Property.FindReferencesInDocumentAsync( - property, state, - options with { AssociatePropertyReferencesWithSpecificAccessor = false }, - cancellationToken).ConfigureAwait(false); - - var accessorReferences = propertyReferences.WhereAsArray( - loc => + await ReferenceFinders.Property.FindReferencesInDocumentAsync( + property, + state, + static (loc, data) => { var accessors = GetReferencedAccessorSymbols( - state, property, loc.Node, cancellationToken); - return accessors.Contains(symbol); - }); - - return references.Concat(accessorReferences); + data.state, data.property, loc.Node, data.cancellationToken); + if (accessors.Contains(data.symbol)) + data.processResult(loc, data.processResultData); + }, + (property, symbol, state, processResult, processResultData, cancellationToken), + options with { AssociatePropertyReferencesWithSpecificAccessor = false }, + cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs index 0f228294634e1..cb70c9670db68 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertySymbolReferenceFinder.cs @@ -118,39 +118,44 @@ protected sealed override async Task DetermineDocumentsToSearchAsync( private static bool IsForEachProperty(IPropertySymbol symbol) => symbol.Name == WellKnownMemberNames.CurrentPropertyName; - protected sealed override async ValueTask> FindReferencesInDocumentAsync( + protected sealed override async ValueTask FindReferencesInDocumentAsync( IPropertySymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var nameReferences = await FindReferencesInDocumentUsingSymbolNameAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - - if (options.AssociatePropertyReferencesWithSpecificAccessor) - { - // We want to associate property references to a specific accessor (if an accessor - // is being referenced). Check if this reference would match an accessor. If so, do - // not add it. It will be added by PropertyAccessorSymbolReferenceFinder. - nameReferences = nameReferences.WhereAsArray(loc => + await FindReferencesInDocumentUsingSymbolNameAsync( + symbol, + state, + static (loc, data) => { - var accessors = GetReferencedAccessorSymbols( - state, symbol, loc.Node, cancellationToken); - return accessors.IsEmpty; - }); - } + var useResult = true; + if (data.options.AssociatePropertyReferencesWithSpecificAccessor) + { + // We want to associate property references to a specific accessor (if an accessor + // is being referenced). Check if this reference would match an accessor. If so, do + // not add it. It will be added by PropertyAccessorSymbolReferenceFinder. + var accessors = GetReferencedAccessorSymbols( + data.state, data.symbol, loc.Node, data.cancellationToken); + useResult = accessors.IsEmpty; + } + + if (useResult) + data.processResult(loc, data.processResultData); + }, + processResultData: (self: this, symbol, state, processResult, processResultData, options, cancellationToken), + cancellationToken).ConfigureAwait(false); - var forEachReferences = IsForEachProperty(symbol) - ? await FindReferencesInForEachStatementsAsync(symbol, state, cancellationToken).ConfigureAwait(false) - : []; + if (IsForEachProperty(symbol)) + await FindReferencesInForEachStatementsAsync(symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); - var indexerReferences = symbol.IsIndexer - ? await FindIndexerReferencesAsync(symbol, state, options, cancellationToken).ConfigureAwait(false) - : []; + if (symbol.IsIndexer) + await FindIndexerReferencesAsync(symbol, state, processResult, processResultData, options, cancellationToken).ConfigureAwait(false); - var suppressionReferences = await FindReferencesInDocumentInsideGlobalSuppressionsAsync( - symbol, state, cancellationToken).ConfigureAwait(false); - return nameReferences.Concat(forEachReferences, indexerReferences, suppressionReferences); + await FindReferencesInDocumentInsideGlobalSuppressionsAsync( + symbol, state, processResult, processResultData, cancellationToken).ConfigureAwait(false); } private static Task FindDocumentWithExplicitOrImplicitElementAccessExpressionsAsync( @@ -167,9 +172,11 @@ private static Task FindDocumentWithIndexerMemberCrefAsync( project, documents, static index => index.ContainsIndexerMemberCref, processResult, processResultData, cancellationToken); } - private static async Task> FindIndexerReferencesAsync( + private static async Task FindIndexerReferencesAsync( IPropertySymbol symbol, FindReferencesDocumentState state, + Action processResult, + TData processResultData, FindReferencesSearchOptions options, CancellationToken cancellationToken) { @@ -177,7 +184,7 @@ private static async Task> FindIndexerReferencesA { // Looking for individual get/set references. Don't find anything here. // these results will be provided by the PropertyAccessorSymbolReferenceFinder - return []; + return; } var syntaxFacts = state.SyntaxFacts; @@ -188,7 +195,6 @@ private static async Task> FindIndexerReferencesA syntaxFacts.IsImplicitElementAccess(node) || syntaxFacts.IsConditionalAccessExpression(node) || syntaxFacts.IsIndexerMemberCref(node)); - using var _ = ArrayBuilder.GetInstance(out var locations); foreach (var node in indexerReferenceExpressions) { @@ -202,14 +208,13 @@ private static async Task> FindIndexerReferencesA var location = state.SyntaxTree.GetLocation(new TextSpan(indexerReference.SpanStart, 0)); var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - locations.Add(new FinderLocation(node, + var result = new FinderLocation(node, new ReferenceLocation( state.Document, alias: null, location, isImplicit: false, symbolUsageInfo, GetAdditionalFindUsagesProperties(node, state), - candidateReason))); + candidateReason)); + processResult(result, processResultData); } - - return locations.ToImmutableAndClear(); } private static ValueTask<(bool matched, CandidateReason reason, SyntaxNode indexerReference)> ComputeIndexerInformationAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs new file mode 100644 index 0000000000000..5696c32d2e8b9 --- /dev/null +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StandardCallbacks.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.PooledObjects; + +namespace Microsoft.CodeAnalysis.FindSymbols; + +internal static class StandardCallbacks +{ + public static readonly Action> AddToHashSet = + static (data, set) => set.Add(data); + + public static readonly Action> AddToArrayBuilder = + static (data, builder) => builder.Add(data); +} From 5134c93010c93652a0a47c1cb9a95fde4d96b58b Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Sat, 20 Apr 2024 10:58:22 -0700 Subject: [PATCH 171/292] Add single value cache to BloomFilter hash calculation (#73103) Add single value cache to BloomFilter hash calculation Although this isn't horribly expensive, I noticed that we typically call BloomFilter.ProbablyContains many times with the same input string. This string is then hashed a number of times (about 13 times if the string is present, less if not). Instead, we can cache what these hashes are as almost all created bloom filters will calculate the same hashes. Testing yielded around a 99% hit rate. Should have a *slight* positive effect for find references and other scenarios using the bloom filters. --- .../Test/Utilities/BloomFilterTests.cs | 110 ++++++++++++++-- .../Portable/Shared/Utilities/BloomFilter.cs | 124 +++++++++++++++--- 2 files changed, 206 insertions(+), 28 deletions(-) diff --git a/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs b/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs index d8fd33a73fc2c..33bff5542e174 100644 --- a/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs +++ b/src/EditorFeatures/Test/Utilities/BloomFilterTests.cs @@ -41,7 +41,8 @@ private static string GenerateString(int value) return builder.ToString(); } - private static void Test(bool isCaseSensitive) + [Theory, CombinatorialData] + public void Test(bool isCaseSensitive) { var comparer = isCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; var strings = new HashSet(GenerateStrings(2000).Skip(500).Take(1000), comparer); @@ -79,14 +80,6 @@ private static void Test(bool isCaseSensitive) } } - [Fact] - public void Test1() - => Test(isCaseSensitive: true); - - [Fact] - public void TestInsensitive() - => Test(isCaseSensitive: false); - [Fact] public void TestEmpty() { @@ -106,6 +99,33 @@ public void TestEmpty() } } + [Fact] + public void TestCacheWhenEmpty() + { + BloomFilter.BloomFilterHash.ResetCachedEntry(); + + _ = new BloomFilter(falsePositiveProbability: 0.0001, isCaseSensitive: false, []); + + Assert.False(BloomFilter.BloomFilterHash.TryGetCachedEntry(out _, out _)); + } + + [Fact] + public void TestCacheAfterCalls() + { + var filter1 = new BloomFilter(falsePositiveProbability: 0.0001, isCaseSensitive: false, []); + var filter2 = new BloomFilter(falsePositiveProbability: 0.0001, isCaseSensitive: true, []); + + _ = filter1.ProbablyContains("test1"); + Assert.True(BloomFilter.BloomFilterHash.TryGetCachedEntry(out var isCaseSensitive, out var value)); + Assert.True(!isCaseSensitive); + Assert.Equal("test1", value); + + _ = filter2.ProbablyContains("test2"); + Assert.True(BloomFilter.BloomFilterHash.TryGetCachedEntry(out isCaseSensitive, out value)); + Assert.True(isCaseSensitive); + Assert.Equal("test2", value); + } + [Fact] public void TestSerialization() { @@ -180,6 +200,78 @@ public void TestInt64() } } + [Theory, CombinatorialData] + public void TestCacheCorrectness(bool isCaseSensitive, bool reverse) + { + var allStringsToTest = GenerateStrings(100_000); + + var comparer = isCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + var allHashSets = new List> + { + new HashSet(GenerateStrings(1_000), comparer), + new HashSet(GenerateStrings(1_000).Where((s, i) => i % 1 == 0), comparer), + new HashSet(GenerateStrings(1_000).Where((s, i) => i % 1 == 1), comparer), + new HashSet(GenerateStrings(10_000), comparer), + new HashSet(GenerateStrings(10_000).Where((s, i) => i % 1 == 0), comparer), + new HashSet(GenerateStrings(10_000).Where((s, i) => i % 1 == 1), comparer), + new HashSet(GenerateStrings(100_000), comparer), + new HashSet(GenerateStrings(100_000).Where((s, i) => i % 1 == 0), comparer), + new HashSet(GenerateStrings(100_000).Where((s, i) => i % 1 == 1), comparer), + }; + + // Try the patterns where we're searching smaller filters then larger ones. Then the pattern of larger ones then smaller ones. + if (reverse) + allHashSets.Reverse(); + + // Try several different probability levels to ensure we maintain the correct false positive rate. We + // must always preserve the true 0 negative rate. + for (var d = 0.1; d >= 0.0001; d /= 10) + { + // Get a bloom filter for each set of strings. + var allFilters = allHashSets.Select(s => new BloomFilter(d, isCaseSensitive, s)).ToArray(); + + // The double array stores the correct/incorrect count per run. + var allCounts = allHashSets.Select(_ => new double[2]).ToArray(); + + // We want to take each string, and test it against each bloom filter. This will ensure that the caches + // we have when computing against one bloom filter don't infect the results of the other bloom filters. + foreach (var test in allStringsToTest) + { + for (var i = 0; i < allHashSets.Count; i++) + { + var strings = allHashSets[i]; + var filter = allFilters[i]; + var counts = allCounts[i]; + var actualContains = strings.Contains(test); + var filterContains = filter.ProbablyContains(test); + + // if the filter says no, then it can't be in the real set. + if (!filterContains) + Assert.False(actualContains); + + if (actualContains == filterContains) + { + counts[0]++; + } + else + { + counts[1]++; + } + } + } + + // Now validate for this set of bloom filters, and this particular probability level, that all the + // rates remain correct for each bloom filter. + foreach (var counts in allCounts) + { + var correctCount = counts[0]; + var incorrectCount = counts[1]; + var falsePositivePercentage = incorrectCount / (correctCount + incorrectCount); + Assert.True(falsePositivePercentage < (d * 1.5), string.Format("falsePositivePercentage={0}, d={1}", falsePositivePercentage, d)); + } + } + } + private static HashSet CreateLongs(List ints) { var result = new HashSet(); diff --git a/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs b/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs index 6cc6cad8d4b1a..4d740090dc8a4 100644 --- a/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs +++ b/src/Workspaces/Core/Portable/Shared/Utilities/BloomFilter.cs @@ -5,6 +5,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; namespace Microsoft.CodeAnalysis.Shared.Utilities; @@ -113,7 +114,7 @@ private static int ComputeK(int expectedCount, double falsePositiveProbability) /// /// Murmur hash is public domain. Actual code is included below as reference. /// - private int ComputeHash(string key, int seed) + private static int ComputeHash(string key, int seed, bool isCaseSensitive) { unchecked { @@ -127,8 +128,8 @@ private int ComputeHash(string key, int seed) var index = 0; while (numberOfCharsLeft >= 2) { - var c1 = GetCharacter(key, index); - var c2 = GetCharacter(key, index + 1); + var c1 = GetCharacter(key, index, isCaseSensitive); + var c2 = GetCharacter(key, index + 1, isCaseSensitive); h = CombineTwoCharacters(h, c1, c2); @@ -140,7 +141,7 @@ private int ComputeHash(string key, int seed) // odd length. if (numberOfCharsLeft == 1) { - var c = GetCharacter(key, index); + var c = GetCharacter(key, index, isCaseSensitive); h = CombineLastCharacter(h, c); } @@ -225,10 +226,10 @@ private static uint CombineTwoCharacters(uint h, uint c1, uint c2) } } - private char GetCharacter(string key, int index) + private static char GetCharacter(string key, int index, bool isCaseSensitive) { var c = key[index]; - return _isCaseSensitive ? c : char.ToLowerInvariant(c); + return isCaseSensitive ? c : char.ToLowerInvariant(c); } private static char GetCharacter(long key, int index) @@ -319,13 +320,13 @@ public void Add(string value) { for (var i = 0; i < _hashFunctionCount; i++) { - _bitArray[GetBitArrayIndex(value, i)] = true; + var hash = ComputeHash(value, i, _isCaseSensitive); + _bitArray[GetBitArrayIndexFromHash(hash)] = true; } } - private int GetBitArrayIndex(string value, int i) + private int GetBitArrayIndexFromHash(int hash) { - var hash = ComputeHash(value, i); hash %= _bitArray.Length; return Math.Abs(hash); } @@ -334,22 +335,24 @@ public void Add(long value) { for (var i = 0; i < _hashFunctionCount; i++) { - _bitArray[GetBitArrayIndex(value, i)] = true; + var hash = ComputeHash(value, i); + _bitArray[GetBitArrayIndexFromHash(hash)] = true; } } - private int GetBitArrayIndex(long value, int i) - { - var hash = ComputeHash(value, i); - hash %= _bitArray.Length; - return Math.Abs(hash); - } - public bool ProbablyContains(string value) { + // Request an array of immutable hashes for this input. Note that it's possible + // that the returned array might return a cached entry calculated by a different + // bloom filter and thus might have more entries than we need, but it's ok as + // it's guaranteed that the first _hashFunctionCount of those values are the values + // we would have computed had we not used the cache. + var hashes = BloomFilterHash.GetOrCreateHashArray(value, _isCaseSensitive, _hashFunctionCount); + for (var i = 0; i < _hashFunctionCount; i++) { - if (!_bitArray[GetBitArrayIndex(value, i)]) + var hash = hashes[i]; + if (!_bitArray[GetBitArrayIndexFromHash(hash)]) { return false; } @@ -362,7 +365,8 @@ public bool ProbablyContains(long value) { for (var i = 0; i < _hashFunctionCount; i++) { - if (!_bitArray[GetBitArrayIndex(value, i)]) + var hash = ComputeHash(value, i); + if (!_bitArray[GetBitArrayIndexFromHash(hash)]) { return false; } @@ -395,4 +399,86 @@ private static bool IsEquivalent(BitArray array1, BitArray array2) return true; } + + /// + /// Provides mechanism to efficiently obtain bloom filter hash for a value. Backed by a single element cache. + /// + internal sealed class BloomFilterHash + { + private static BloomFilterHash? s_cachedHash; + + private readonly string _value; + private readonly bool _isCaseSensitive; + private readonly ImmutableArray _hashes; + + private BloomFilterHash(string value, bool isCaseSensitive, int hashFunctionCount) + { + _value = value; + _isCaseSensitive = isCaseSensitive; + + var hashBuilder = new FixedSizeArrayBuilder(hashFunctionCount); + + for (var i = 0; i < hashFunctionCount; i++) + hashBuilder.Add(BloomFilter.ComputeHash(value, i, _isCaseSensitive)); + + _hashes = hashBuilder.MoveToImmutable(); + } + + /// + /// Although calculating this hash isn't terribly expensive, it does involve multiple + /// (usually around 13) hashings of the string (the actual count is ). + /// The typical usage pattern of bloom filters is that some operation (eg: find references) + /// requires asking a multitude of bloom filters whether a particular value is likely contained. + /// The vast majority of those bloom filters will end up hashing that string to the same values, so + /// we put those values into a simple cache and see if it can be used before calculating. + /// Local testing has put the hit rate of this at around 99%. + /// + /// Note that it's possible for this method to return an array from the cache longer than hashFunctionCount, + /// but if so, it's guaranteed that the values returned in the first hashFunctionCount entries are + /// the same as if the cache hadn't been used. + /// + public static ImmutableArray GetOrCreateHashArray(string value, bool isCaseSensitive, int hashFunctionCount) + { + var cachedHash = s_cachedHash; + + // Not an equivalency check on the hashFunctionCount as a longer array is ok. This is because the + // values in the array are determined by value and isCaseSensitive and hashFunctionCount is simply + // used to determine the length of the returned array. As long as the cached entry matches the value + // and isCaseSensitive and is at least as long as we need, then we can use it. + if (cachedHash == null + || cachedHash._isCaseSensitive != isCaseSensitive + || cachedHash._hashes.Length < hashFunctionCount + || cachedHash._value != value) + { + cachedHash = new BloomFilterHash(value, isCaseSensitive, hashFunctionCount); + s_cachedHash = cachedHash; + } + + return cachedHash._hashes; + } + + // Used only by tests + internal static bool TryGetCachedEntry(out bool isCaseSensitive, out string value) + { + var cachedHash = s_cachedHash; + + if (cachedHash == null) + { + isCaseSensitive = false; + value = string.Empty; + + return false; + } + + isCaseSensitive = cachedHash._isCaseSensitive; + value = cachedHash._value; + + return true; + } + + internal static void ResetCachedEntry() + { + s_cachedHash = null; + } + } } From 43c9becc3914aa8e44fcbc374a9e279c2ec3b962 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 11:22:22 -0700 Subject: [PATCH 172/292] remove unused method --- .../Serialization/SerializerService_Reference.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 25ccba1f9b3a4..f45d6fce6e9e0 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -415,21 +415,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT } } - private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) - { - Contract.ThrowIfFalse(SerializationKinds.Bits == kind); - - var array = reader.ReadByteArray(); - - // Pin the array so that the module metadata can treat it as a segment of unmanaged memory. - var pinnedObject = new PinnedObject(array); - - // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in - // as the onDispose callback of the metadata. - return ModuleMetadata.CreateFromMetadata( - pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); - } - private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); From 7fd00210d3e0980ed8c29eda2dabbc71e9654e57 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 11:22:47 -0700 Subject: [PATCH 173/292] remove pinned object concept --- .../SerializerService_Reference.cs | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f45d6fce6e9e0..f93bdbb67646e 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -459,35 +459,6 @@ private static void WriteUnresolvedAnalyzerReferenceTo(AnalyzerReference referen } } - private sealed class PinnedObject : IDisposable - { - // shouldn't be read-only since GCHandle is a mutable struct - private GCHandle _gcHandle; - - public PinnedObject(byte[] array) - => _gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); - - internal IntPtr GetPointer() - => _gcHandle.AddrOfPinnedObject(); - - private void OnDispose() - { - if (_gcHandle.IsAllocated) - { - _gcHandle.Free(); - } - } - - ~PinnedObject() - => OnDispose(); - - public void Dispose() - { - GC.SuppressFinalize(this); - OnDispose(); - } - } - private sealed class MissingMetadataReference : PortableExecutableReference { private readonly DocumentationProvider _provider; From 30cb1ae32b4e7262ef46152869cb227e9282f352 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 11:24:19 -0700 Subject: [PATCH 174/292] duplicate to make tracking down issue easier --- .../SerializerService_Reference.cs | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f93bdbb67646e..82101f4abec48 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -390,28 +390,44 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); storageIdentifier = storageHandle.Identifier; + + // Now read in the module data using that identifier. This will either be reading from the host's memory if + // they passed us the information about that memory segment. Or it will be reading from our own memory if they + // sent us the full contents. + var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); + + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. + unsafe + { + var metadata = ModuleMetadata.CreateFromMetadata( + (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); + return (metadata, storageIdentifier); + } } else { // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); - } - // Now read in the module data using that identifier. This will either be reading from the host's memory if - // they passed us the information about that memory segment. Or it will be reading from our own memory if they - // sent us the full contents. - var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); + // Now read in the module data using that identifier. This will either be reading from the host's memory if + // they passed us the information about that memory segment. Or it will be reading from our own memory if they + // sent us the full contents. + var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. - unsafe - { - var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - return (metadata, storageIdentifier); + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. + unsafe + { + var metadata = ModuleMetadata.CreateFromMetadata( + (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); + return (metadata, storageIdentifier); + } } } From efab42e9130ae12bc38703882d25fe949df3822d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 11:27:10 -0700 Subject: [PATCH 175/292] Break into helper methods --- .../SerializerService_Reference.cs | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 82101f4abec48..21344c00bc1e2 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -376,43 +376,37 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); - long length; - TemporaryStorageIdentifier storageIdentifier; - if (kind == SerializationKinds.Bits) + return kind == SerializationKinds.Bits + ? ReadModuleMetadataFromBits() + : ReadModuleMetadataFromMemoryMappedFile(); + + (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromMemoryMappedFile() + { + // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it + // will not be released by the host. + var storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); + return ReadModuleMetadataFromStorage(storageIdentifier); + } + + (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromBits() { // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the // server side so that we can refer to this data uniformly. using var stream = SerializableBytes.CreateWritableStream(); CopyByteArrayToStream(reader, stream, cancellationToken); - length = stream.Length; + var length = stream.Length; stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); - storageIdentifier = storageHandle.Identifier; + var storageIdentifier = storageHandle.Identifier; - // Now read in the module data using that identifier. This will either be reading from the host's memory if - // they passed us the information about that memory segment. Or it will be reading from our own memory if they - // sent us the full contents. - var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); - - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. - unsafe - { - var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - return (metadata, storageIdentifier); - } + return ReadModuleMetadataFromStorage(storageIdentifier); } - else - { - // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it - // will not be released by the host. - storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); + (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromStorage( + TemporaryStorageIdentifier storageIdentifier) + { // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. From 1a9005cc5eaf355f742f6dbc9b3616ba13e57482 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 12:53:48 -0700 Subject: [PATCH 176/292] Tweak --- .../Portable/Serialization/SerializerService_Reference.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 21344c00bc1e2..748cdcc70ca08 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -413,13 +413,10 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. unsafe { var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); + (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length); return (metadata, storageIdentifier); } } From 066476e9756986dcc72ffa8e5833cd6a14f8e3e6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 14:25:11 -0700 Subject: [PATCH 177/292] rename file' --- .../TemporaryStorageService.cs | 456 ++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs new file mode 100644 index 0000000000000..b3fd55389ce4c --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -0,0 +1,456 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.IO.MemoryMappedFiles; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Temporarily stores text and streams in memory mapped files. +/// +#if NETCOREAPP +[SupportedOSPlatform("windows")] +#endif +internal sealed partial class TemporaryStorageService : ITemporaryStorageServiceInternal +{ + /// + /// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other storage + /// units. + /// + /// + /// The value of 256k reduced the number of files dumped to separate memory mapped files by 60% compared to + /// the next lower power-of-2 size for Roslyn.sln itself. + /// + /// + private const long SingleFileThreshold = 256 * 1024; + + /// + /// The size in bytes of a memory mapped file created to store multiple temporary objects. + /// + /// + /// This value (8mb) creates roughly 35 memory mapped files (around 300MB) to store the contents of all of + /// Roslyn.sln a snapshot. This keeps the data safe, so that we can drop it from memory when not needed, but + /// reconstitute the contents we originally had in the snapshot in case the original files change on disk. + /// + /// + private const long MultiFileBlockSize = SingleFileThreshold * 32; + + private readonly IWorkspaceThreadingService? _workspaceThreadingService; + private readonly ITextFactoryService _textFactory; + + /// + /// The synchronization object for accessing the memory mapped file related fields (indicated in the remarks + /// of each field). + /// + /// + /// PERF DEV NOTE: A concurrent (but complex) implementation of this type with identical semantics is + /// available in source control history. The use of exclusive locks was not causing any measurable + /// performance overhead even on 28-thread machines at the time this was written. + /// + private readonly object _gate = new(); + + /// + /// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer + /// allocation until space is no longer available in it. Access should be synchronized on + /// + private ReferenceCountedDisposable.WeakReference _weakFileReference; + + /// The name of the current memory mapped file for multiple storage units. Access should be synchronized on + /// + /// + private string? _name; + + /// The total size of the current memory mapped file for multiple storage units. Access should be + /// synchronized on + /// + private long _fileSize; + + /// + /// The offset into the current memory mapped file where the next storage unit can be held. Access should be + /// synchronized on . + /// + /// + private long _offset; + + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + private TemporaryStorageService(IWorkspaceThreadingService? workspaceThreadingService, ITextFactoryService textFactory) + { + _workspaceThreadingService = workspaceThreadingService; + _textFactory = textFactory; + } + + public ITemporaryTextStorageInternal CreateTemporaryTextStorage() + => new TemporaryTextStorage(this); + + public TemporaryTextStorage AttachTemporaryTextStorage( + string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) + => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); + + public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage(this); + storage.WriteStream(stream, cancellationToken); + var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); + return new(storage, identifier); + } + + Stream ITemporaryStorageServiceInternal.ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + => ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); + + public UnmanagedMemoryStream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); + return storage.ReadStream(cancellationToken); + } + + internal TemporaryStorageHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) + { + var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); + return new(storage, storageIdentifier); + } + + /// + /// Allocate shared storage of a specified size. + /// + /// + /// "Small" requests are fulfilled from oversized memory mapped files which support several individual + /// storage units. Larger requests are allocated in their own memory mapped files. + /// + /// The size of the shared storage block to allocate. + /// A describing the allocated block. + private MemoryMappedInfo CreateTemporaryStorage(long size) + { + if (size >= SingleFileThreshold) + { + // Larger blocks are allocated separately + var mapName = CreateUniqueName(size); + var storage = MemoryMappedFile.CreateNew(mapName, size); + return new MemoryMappedInfo(new ReferenceCountedDisposable(storage), mapName, offset: 0, size: size); + } + + lock (_gate) + { + // Obtain a reference to the memory mapped file, creating one if necessary. If a reference counted + // handle to a memory mapped file is obtained in this section, it must either be disposed before + // returning or returned to the caller who will own it through the MemoryMappedInfo. + var reference = _weakFileReference.TryAddReference(); + if (reference == null || _offset + size > _fileSize) + { + var mapName = CreateUniqueName(MultiFileBlockSize); + var file = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize); + + reference = new ReferenceCountedDisposable(file); + _weakFileReference = new ReferenceCountedDisposable.WeakReference(reference); + _name = mapName; + _fileSize = MultiFileBlockSize; + _offset = size; + return new MemoryMappedInfo(reference, _name, offset: 0, size: size); + } + else + { + // Reserve additional space in the existing storage location + Contract.ThrowIfNull(_name); + _offset += size; + return new MemoryMappedInfo(reference, _name, _offset - size, size); + } + } + } + + public static string CreateUniqueName(long size) + => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); + + public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName + { + private readonly TemporaryStorageService _service; + private SourceHashAlgorithm _checksumAlgorithm; + private Encoding? _encoding; + private ImmutableArray _contentHash; + private MemoryMappedInfo? _memoryMappedInfo; + + public TemporaryTextStorage(TemporaryStorageService service) + => _service = service; + + public TemporaryTextStorage( + TemporaryStorageService service, + string storageName, + long offset, + long size, + SourceHashAlgorithm checksumAlgorithm, + Encoding? encoding, + ImmutableArray contentHash) + { + _service = service; + _checksumAlgorithm = checksumAlgorithm; + _encoding = encoding; + _contentHash = contentHash; + _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); + } + + // TODO: cleanup https://github.com/dotnet/roslyn/issues/43037 + // Offset, Size not accessed if Name is null + public string? Name => _memoryMappedInfo?.Name; + public long Offset => _memoryMappedInfo!.Offset; + public long Size => _memoryMappedInfo!.Size; + + /// + /// Gets the value for the property for the + /// represented by this temporary storage. + /// + public SourceHashAlgorithm ChecksumAlgorithm => _checksumAlgorithm; + + /// + /// Gets the value for the property for the + /// represented by this temporary storage. + /// + public Encoding? Encoding => _encoding; + + /// + /// Gets the checksum for the represented by this temporary storage. This is equivalent + /// to calling . + /// + public ImmutableArray ContentHash => _contentHash; + + public void Dispose() + { + // Destructors of SafeHandle and FileStream in MemoryMappedFile + // will eventually release resources if this Dispose is not called + // explicitly + _memoryMappedInfo?.Dispose(); + + _memoryMappedInfo = null; + _encoding = null; + _contentHash = default; + } + + public SourceText ReadText(CancellationToken cancellationToken) + { + if (_memoryMappedInfo == null) + { + throw new InvalidOperationException(); + } + + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) + { + using var stream = _memoryMappedInfo.CreateReadableStream(); + using var reader = CreateTextReaderFromTemporaryStorage(stream); + + // we pass in encoding we got from original source text even if it is null. + return _service._textFactory.CreateText(reader, _encoding, _checksumAlgorithm, cancellationToken); + } + } + + public async Task ReadTextAsync(CancellationToken cancellationToken) + { + // There is a reason for implementing it like this: proper async implementation + // that reads the underlying memory mapped file stream in an asynchronous fashion + // doesn't actually work. Windows doesn't offer + // any non-blocking way to read from a memory mapped file; the underlying memcpy + // may block as the memory pages back in and that's something you have to live + // with. Therefore, any implementation that attempts to use async will still + // always be blocking at least one threadpool thread in the memcpy in the case + // of a page fault. Therefore, if we're going to be blocking a thread, we should + // just block one thread and do the whole thing at once vs. a fake "async" + // implementation which will continue to requeue work back to the thread pool. + if (_service._workspaceThreadingService is { IsOnMainThread: true }) + { + await Task.Yield().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + return ReadText(cancellationToken); + } + + public void WriteText(SourceText text, CancellationToken cancellationToken) + { + if (_memoryMappedInfo != null) + { + throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); + } + + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteText, cancellationToken)) + { + _checksumAlgorithm = text.ChecksumAlgorithm; + _encoding = text.Encoding; + _contentHash = text.GetContentHash(); + + // the method we use to get text out of SourceText uses Unicode (2bytes per char). + var size = Encoding.Unicode.GetMaxByteCount(text.Length); + _memoryMappedInfo = _service.CreateTemporaryStorage(size); + + // Write the source text out as Unicode. We expect that to be cheap. + using var stream = _memoryMappedInfo.CreateWritableStream(); + using var writer = new StreamWriter(stream, Encoding.Unicode); + + text.Write(writer, cancellationToken); + } + } + + public async Task WriteTextAsync(SourceText text, CancellationToken cancellationToken) + { + // See commentary in ReadTextAsync for why this is implemented this way. + if (_service._workspaceThreadingService is { IsOnMainThread: true }) + { + await Task.Yield().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + WriteText(text, cancellationToken); + } + + private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) + { + var src = (char*)stream.PositionPointer; + + // BOM: Unicode, little endian + // Skip the BOM when creating the reader + Debug.Assert(*src == 0xFEFF); + + return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); + } + } + + internal sealed class TemporaryStreamStorage : IDisposable + { + private readonly TemporaryStorageService _service; + private MemoryMappedInfo? _memoryMappedInfo; + + public TemporaryStreamStorage(TemporaryStorageService service) + => _service = service; + + public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long offset, long size) + { + _service = service; + _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); + } + + public string Name => _memoryMappedInfo!.Name; + public long Offset => _memoryMappedInfo!.Offset; + public long Size => _memoryMappedInfo!.Size; + + public void Dispose() + { + // Destructors of SafeHandle and FileStream in MemoryMappedFile + // will eventually release resources if this Dispose is not called + // explicitly + _memoryMappedInfo?.Dispose(); + _memoryMappedInfo = null; + } + + public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) + { + if (_memoryMappedInfo == null) + { + throw new InvalidOperationException(); + } + + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + return _memoryMappedInfo.CreateReadableStream(); + } + } + + public void WriteStream(Stream stream, CancellationToken cancellationToken) + { + if (_memoryMappedInfo != null) + throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); + + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) + { + var size = stream.Length; + _memoryMappedInfo = _service.CreateTemporaryStorage(size); + using var viewStream = _memoryMappedInfo.CreateWritableStream(); + + using var pooledObject = SharedPools.ByteArray.GetPooledObject(); + var buffer = pooledObject.Object; + while (true) + { + var count = stream.Read(buffer, 0, buffer.Length); + if (count == 0) + break; + + viewStream.Write(buffer, 0, count); + } + } + } + } +} + +internal unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength +{ + private char* _position; + private readonly char* _end; + + public DirectMemoryAccessStreamReader(char* src, int length) + : base(length) + { + RoslynDebug.Assert(src != null); + RoslynDebug.Assert(length >= 0); + + _position = src; + _end = _position + length; + } + + public override int Peek() + { + if (_position >= _end) + { + return -1; + } + + return *_position; + } + + public override int Read() + { + if (_position >= _end) + { + return -1; + } + + return *_position++; + } + + public override int Read(char[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0 || index >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0 || (index + count) > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + count = Math.Min(count, (int)(_end - _position)); + if (count > 0) + { + Marshal.Copy((IntPtr)_position, buffer, index, count); + _position += count; + } + + return count; + } +} From 0b866e5bc9902c5c40ac98ac00331dcd5be0e687 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 14:26:22 -0700 Subject: [PATCH 178/292] Handles' --- .../VisualStudioMetadataReference.Snapshot.cs | 4 +- .../VisualStudioMetadataReferenceManager.cs | 29 +- .../Serialization/ISupportTemporaryStorage.cs | 2 +- .../SerializerService_Reference.cs | 70 +-- .../TemporaryStorageServiceFactory.cs | 450 ------------------ ...CompilationState.SkeletonReferenceCache.cs | 3 +- 6 files changed, 55 insertions(+), 503 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 711aa80dcd26e..8c407e68825d5 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList StorageIdentifiers - => _provider.GetStorageIdentifiers(this.FilePath, _timestamp.Value); + public IReadOnlyList StorageHandles + => _provider.GetStorageHandles(this.FilePath, _timestamp.Value); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 637aa33983542..0ef1f97083953 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -43,7 +43,7 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS /// name/offset/length to the remote process, and it can map that same memory in directly, instead of needing the /// host to send the entire contents of the assembly over the channel to the OOP process. /// - private static readonly ConditionalWeakTable> s_metadataToStorageIdentifiers = new(); + private static readonly ConditionalWeakTable> s_metadataToStorageHandles = new(); private readonly MetadataCache _metadataCache = new(); private readonly ImmutableArray _runtimeDirectories; @@ -86,14 +86,14 @@ public void Dispose() } } - public IReadOnlyList? GetStorageIdentifiers(string fullPath, DateTime snapshotTimestamp) + public IReadOnlyList? GetStorageHandles(string fullPath, DateTime snapshotTimestamp) { var key = new FileKey(fullPath, snapshotTimestamp); // check existing metadata if (_metadataCache.TryGetMetadata(key, out var source) && - s_metadataToStorageIdentifiers.TryGetValue(source, out var storages)) + s_metadataToStorageHandles.TryGetValue(source, out var handler)) { - return storages; + return handler; } return null; @@ -154,17 +154,17 @@ AssemblyMetadata GetMetadataWorker() else { // use temporary storage - using var _ = ArrayBuilder.GetInstance(out var storageIdentifiers); + using var _ = ArrayBuilder.GetInstance(out var storageHandles); var newMetadata = CreateAssemblyMetadata(key, key => { // // - GetMetadataFromTemporaryStorage(key, out var storageIdentifier, out var metadata); - storageIdentifiers.Add(storageIdentifier); + GetMetadataFromTemporaryStorage(key, out var storageHandle, out var metadata); + storageHandles.Add(storageHandle); return metadata; }); - s_metadataToStorageIdentifiers.Add(newMetadata, storageIdentifiers.ToImmutable()); + s_metadataToStorageHandles.Add(newMetadata, storageHandles.ToImmutable()); return newMetadata; } @@ -172,9 +172,9 @@ AssemblyMetadata GetMetadataWorker() } private void GetMetadataFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageIdentifier storageIdentifier, out ModuleMetadata metadata) + FileKey moduleFileKey, out TemporaryStorageHandle storageHandle, out ModuleMetadata metadata) { - GetStorageInfoFromTemporaryStorage(moduleFileKey, out storageIdentifier, out var stream); + GetStorageInfoFromTemporaryStorage(moduleFileKey, out storageHandle, out var stream); unsafe { @@ -185,7 +185,7 @@ private void GetMetadataFromTemporaryStorage( return; void GetStorageInfoFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageIdentifier storageIdentifier, out UnmanagedMemoryStream stream) + FileKey moduleFileKey, out TemporaryStorageHandle storageHandle, out UnmanagedMemoryStream stream) { int size; @@ -215,13 +215,12 @@ void GetStorageInfoFromTemporaryStorage( // location, so we can create a metadata value wrapping that. This will also let us share the memory // for that metadata value with our OOP process. copyStream.Position = 0; - var handle = _temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); - storageIdentifier = handle.Identifier; + storageHandle = _temporaryStorageService.WriteToTemporaryStorage(copyStream, CancellationToken.None); } // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. - stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); - + stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageHandle.Identifier, CancellationToken.None); + GC.KeepAlive(storageHandle); // stream size must be same as what metadata reader said the size should be. Contract.ThrowIfFalse(stream.Length == size); } diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index 2eaee88847d46..ffb6efafc2523 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -14,5 +14,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? StorageIdentifiers { get; } + IReadOnlyList? StorageHandles { get; } } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 748cdcc70ca08..3376cf0c4d145 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -64,9 +64,9 @@ public virtual void WriteMetadataReferenceTo(MetadataReference reference, Object { if (reference is PortableExecutableReference portable) { - if (portable is ISupportTemporaryStorage { StorageIdentifiers: { Count: > 0 } storageIdentifiers } && + if (portable is ISupportTemporaryStorage { StorageHandles: { Count: > 0 } handles } && TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( - portable, storageIdentifiers, writer, cancellationToken)) + portable, handles, writer, cancellationToken)) { return; } @@ -232,7 +232,7 @@ private PortableExecutableReference ReadPortableExecutableReferenceFrom(ObjectRe var filePath = reader.ReadString(); - if (TryReadMetadataFrom(reader, kind, cancellationToken) is not (var metadata, var storageIdentifiers)) + if (TryReadMetadataFrom(reader, kind, cancellationToken) is not (var metadata, var storageHandles)) { // TODO: deal with xml document provider properly // should we shadow copy xml doc comment? @@ -252,7 +252,7 @@ private PortableExecutableReference ReadPortableExecutableReferenceFrom(ObjectRe _documentationService.GetDocumentationProvider(filePath) : XmlDocumentationProvider.Default; return new SerializedMetadataReference( - properties, filePath, metadata, storageIdentifiers, documentProvider); + properties, filePath, metadata, storageHandles, documentProvider); } private static void WriteTo(MetadataReferenceProperties properties, ObjectWriter writer, CancellationToken cancellationToken) @@ -310,27 +310,27 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( PortableExecutableReference reference, - IReadOnlyList storageIdentifiers, + IReadOnlyList handles, ObjectWriter writer, CancellationToken cancellationToken) { - Contract.ThrowIfTrue(storageIdentifiers.Count == 0); + Contract.ThrowIfTrue(handles.Count == 0); WritePortableExecutableReferenceHeaderTo(reference, SerializationKinds.MemoryMapFile, writer, cancellationToken); writer.WriteInt32((int)MetadataImageKind.Assembly); - writer.WriteInt32(storageIdentifiers.Count); + writer.WriteInt32(handles.Count); - foreach (var identifier in storageIdentifiers) + foreach (var handle in handles) { writer.WriteInt32((int)MetadataImageKind.Module); - identifier.WriteTo(writer); + handle.Identifier.WriteTo(writer); } return true; } - private (Metadata metadata, ImmutableArray storageIdentifiers)? TryReadMetadataFrom( + private (Metadata metadata, ImmutableArray storageHandles)? TryReadMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { var imageKind = reader.ReadInt32(); @@ -344,7 +344,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT if (metadataKind == MetadataImageKind.Assembly) { using var pooledMetadata = Creator.CreateList(); - using var pooledStorageIdentifiers = Creator.CreateList(); + using var pooledHandles = Creator.CreateList(); var count = reader.ReadInt32(); for (var i = 0; i < count; i++) @@ -352,24 +352,24 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT metadataKind = (MetadataImageKind)reader.ReadInt32(); Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - var (metadata, storageIdentifier) = ReadModuleMetadataFrom(reader, kind, cancellationToken); + var (metadata, storageHandle) = ReadModuleMetadataFrom(reader, kind, cancellationToken); pooledMetadata.Object.Add(metadata); - pooledStorageIdentifiers.Object.Add(storageIdentifier); + pooledHandles.Object.Add(storageHandle); } - return (AssemblyMetadata.Create(pooledMetadata.Object), pooledStorageIdentifiers.Object.ToImmutableArrayOrEmpty()); + return (AssemblyMetadata.Create(pooledMetadata.Object), pooledHandles.Object.ToImmutableArrayOrEmpty()); } else { Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); var moduleInfo = ReadModuleMetadataFrom(reader, kind, cancellationToken); - return (moduleInfo.metadata, [moduleInfo.storageIdentifier]); + return (moduleInfo.metadata, [moduleInfo.storageHandle]); } } - private (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFrom( + private (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -380,15 +380,16 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT ? ReadModuleMetadataFromBits() : ReadModuleMetadataFromMemoryMappedFile(); - (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromMemoryMappedFile() + (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromMemoryMappedFile() { // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. var storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); - return ReadModuleMetadataFromStorage(storageIdentifier); + var storageHandle = _storageService.GetHandle(storageIdentifier); + return ReadModuleMetadataFromStorage(storageHandle); } - (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromBits() + (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromBits() { // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the // server side so that we can refer to this data uniformly. @@ -399,25 +400,26 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); - var storageIdentifier = storageHandle.Identifier; - - return ReadModuleMetadataFromStorage(storageIdentifier); + return ReadModuleMetadataFromStorage(storageHandle); } - (ModuleMetadata metadata, TemporaryStorageIdentifier storageIdentifier) ReadModuleMetadataFromStorage( - TemporaryStorageIdentifier storageIdentifier) + (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromStorage( + TemporaryStorageHandle storageHandle) { // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. - var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - Contract.ThrowIfFalse(storageIdentifier.Size == unmanagedStream.Length); + var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageHandle.Identifier, cancellationToken); + Contract.ThrowIfFalse(storageHandle.Identifier.Size == unmanagedStream.Length); + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. unsafe { var metadata = ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length); - return (metadata, storageIdentifier); + (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); + return (metadata, storageHandle); } } } @@ -503,22 +505,22 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private sealed class SerializedMetadataReference : PortableExecutableReference, ISupportTemporaryStorage { private readonly Metadata _metadata; - private readonly ImmutableArray _storageIdentifiers; + private readonly ImmutableArray _storageHandles; private readonly DocumentationProvider _provider; - public IReadOnlyList StorageIdentifiers => _storageIdentifiers; + public IReadOnlyList StorageHandles => _storageHandles; public SerializedMetadataReference( MetadataReferenceProperties properties, string? fullPath, Metadata metadata, - ImmutableArray storagesIdentifiers, + ImmutableArray storageHandles, DocumentationProvider initialDocumentation) : base(properties, fullPath, initialDocumentation) { - Contract.ThrowIfTrue(storagesIdentifiers.IsDefault); + Contract.ThrowIfTrue(storageHandles.IsDefault); _metadata = metadata; - _storageIdentifiers = storagesIdentifiers; + _storageHandles = storageHandles; _provider = initialDocumentation; } @@ -533,6 +535,6 @@ protected override Metadata GetMetadataImpl() => _metadata; protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties) - => new SerializedMetadataReference(properties, FilePath, _metadata, _storageIdentifiers, _provider); + => new SerializedMetadataReference(properties, FilePath, _metadata, _storageHandles, _provider); } } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs deleted file mode 100644 index 365a2870ab1cb..0000000000000 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ /dev/null @@ -1,450 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.IO.MemoryMappedFiles; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Host; - -/// -/// Temporarily stores text and streams in memory mapped files. -/// -#if NETCOREAPP -[SupportedOSPlatform("windows")] -#endif -internal sealed partial class TemporaryStorageService : ITemporaryStorageServiceInternal -{ - /// - /// The maximum size in bytes of a single storage unit in a memory mapped file which is shared with other storage - /// units. - /// - /// - /// The value of 256k reduced the number of files dumped to separate memory mapped files by 60% compared to - /// the next lower power-of-2 size for Roslyn.sln itself. - /// - /// - private const long SingleFileThreshold = 256 * 1024; - - /// - /// The size in bytes of a memory mapped file created to store multiple temporary objects. - /// - /// - /// This value (8mb) creates roughly 35 memory mapped files (around 300MB) to store the contents of all of - /// Roslyn.sln a snapshot. This keeps the data safe, so that we can drop it from memory when not needed, but - /// reconstitute the contents we originally had in the snapshot in case the original files change on disk. - /// - /// - private const long MultiFileBlockSize = SingleFileThreshold * 32; - - private readonly IWorkspaceThreadingService? _workspaceThreadingService; - private readonly ITextFactoryService _textFactory; - - /// - /// The synchronization object for accessing the memory mapped file related fields (indicated in the remarks - /// of each field). - /// - /// - /// PERF DEV NOTE: A concurrent (but complex) implementation of this type with identical semantics is - /// available in source control history. The use of exclusive locks was not causing any measurable - /// performance overhead even on 28-thread machines at the time this was written. - /// - private readonly object _gate = new(); - - /// - /// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer - /// allocation until space is no longer available in it. Access should be synchronized on - /// - private ReferenceCountedDisposable.WeakReference _weakFileReference; - - /// The name of the current memory mapped file for multiple storage units. Access should be synchronized on - /// - /// - private string? _name; - - /// The total size of the current memory mapped file for multiple storage units. Access should be - /// synchronized on - /// - private long _fileSize; - - /// - /// The offset into the current memory mapped file where the next storage unit can be held. Access should be - /// synchronized on . - /// - /// - private long _offset; - - [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] - private TemporaryStorageService(IWorkspaceThreadingService? workspaceThreadingService, ITextFactoryService textFactory) - { - _workspaceThreadingService = workspaceThreadingService; - _textFactory = textFactory; - } - - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - => new TemporaryTextStorage(this); - - public TemporaryTextStorage AttachTemporaryTextStorage( - string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) - => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); - - public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) - { - var storage = new TemporaryStreamStorage(this); - storage.WriteStream(stream, cancellationToken); - var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); - return new(storage, identifier); - } - - Stream ITemporaryStorageServiceInternal.ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) - => ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - - public UnmanagedMemoryStream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) - { - var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); - return storage.ReadStream(cancellationToken); - } - - /// - /// Allocate shared storage of a specified size. - /// - /// - /// "Small" requests are fulfilled from oversized memory mapped files which support several individual - /// storage units. Larger requests are allocated in their own memory mapped files. - /// - /// The size of the shared storage block to allocate. - /// A describing the allocated block. - private MemoryMappedInfo CreateTemporaryStorage(long size) - { - if (size >= SingleFileThreshold) - { - // Larger blocks are allocated separately - var mapName = CreateUniqueName(size); - var storage = MemoryMappedFile.CreateNew(mapName, size); - return new MemoryMappedInfo(new ReferenceCountedDisposable(storage), mapName, offset: 0, size: size); - } - - lock (_gate) - { - // Obtain a reference to the memory mapped file, creating one if necessary. If a reference counted - // handle to a memory mapped file is obtained in this section, it must either be disposed before - // returning or returned to the caller who will own it through the MemoryMappedInfo. - var reference = _weakFileReference.TryAddReference(); - if (reference == null || _offset + size > _fileSize) - { - var mapName = CreateUniqueName(MultiFileBlockSize); - var file = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize); - - reference = new ReferenceCountedDisposable(file); - _weakFileReference = new ReferenceCountedDisposable.WeakReference(reference); - _name = mapName; - _fileSize = MultiFileBlockSize; - _offset = size; - return new MemoryMappedInfo(reference, _name, offset: 0, size: size); - } - else - { - // Reserve additional space in the existing storage location - Contract.ThrowIfNull(_name); - _offset += size; - return new MemoryMappedInfo(reference, _name, _offset - size, size); - } - } - } - - public static string CreateUniqueName(long size) - => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); - - public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName - { - private readonly TemporaryStorageService _service; - private SourceHashAlgorithm _checksumAlgorithm; - private Encoding? _encoding; - private ImmutableArray _contentHash; - private MemoryMappedInfo? _memoryMappedInfo; - - public TemporaryTextStorage(TemporaryStorageService service) - => _service = service; - - public TemporaryTextStorage( - TemporaryStorageService service, - string storageName, - long offset, - long size, - SourceHashAlgorithm checksumAlgorithm, - Encoding? encoding, - ImmutableArray contentHash) - { - _service = service; - _checksumAlgorithm = checksumAlgorithm; - _encoding = encoding; - _contentHash = contentHash; - _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); - } - - // TODO: cleanup https://github.com/dotnet/roslyn/issues/43037 - // Offset, Size not accessed if Name is null - public string? Name => _memoryMappedInfo?.Name; - public long Offset => _memoryMappedInfo!.Offset; - public long Size => _memoryMappedInfo!.Size; - - /// - /// Gets the value for the property for the - /// represented by this temporary storage. - /// - public SourceHashAlgorithm ChecksumAlgorithm => _checksumAlgorithm; - - /// - /// Gets the value for the property for the - /// represented by this temporary storage. - /// - public Encoding? Encoding => _encoding; - - /// - /// Gets the checksum for the represented by this temporary storage. This is equivalent - /// to calling . - /// - public ImmutableArray ContentHash => _contentHash; - - public void Dispose() - { - // Destructors of SafeHandle and FileStream in MemoryMappedFile - // will eventually release resources if this Dispose is not called - // explicitly - _memoryMappedInfo?.Dispose(); - - _memoryMappedInfo = null; - _encoding = null; - _contentHash = default; - } - - public SourceText ReadText(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) - { - using var stream = _memoryMappedInfo.CreateReadableStream(); - using var reader = CreateTextReaderFromTemporaryStorage(stream); - - // we pass in encoding we got from original source text even if it is null. - return _service._textFactory.CreateText(reader, _encoding, _checksumAlgorithm, cancellationToken); - } - } - - public async Task ReadTextAsync(CancellationToken cancellationToken) - { - // There is a reason for implementing it like this: proper async implementation - // that reads the underlying memory mapped file stream in an asynchronous fashion - // doesn't actually work. Windows doesn't offer - // any non-blocking way to read from a memory mapped file; the underlying memcpy - // may block as the memory pages back in and that's something you have to live - // with. Therefore, any implementation that attempts to use async will still - // always be blocking at least one threadpool thread in the memcpy in the case - // of a page fault. Therefore, if we're going to be blocking a thread, we should - // just block one thread and do the whole thing at once vs. a fake "async" - // implementation which will continue to requeue work back to the thread pool. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - } - - return ReadText(cancellationToken); - } - - public void WriteText(SourceText text, CancellationToken cancellationToken) - { - if (_memoryMappedInfo != null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteText, cancellationToken)) - { - _checksumAlgorithm = text.ChecksumAlgorithm; - _encoding = text.Encoding; - _contentHash = text.GetContentHash(); - - // the method we use to get text out of SourceText uses Unicode (2bytes per char). - var size = Encoding.Unicode.GetMaxByteCount(text.Length); - _memoryMappedInfo = _service.CreateTemporaryStorage(size); - - // Write the source text out as Unicode. We expect that to be cheap. - using var stream = _memoryMappedInfo.CreateWritableStream(); - using var writer = new StreamWriter(stream, Encoding.Unicode); - - text.Write(writer, cancellationToken); - } - } - - public async Task WriteTextAsync(SourceText text, CancellationToken cancellationToken) - { - // See commentary in ReadTextAsync for why this is implemented this way. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - } - - WriteText(text, cancellationToken); - } - - private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) - { - var src = (char*)stream.PositionPointer; - - // BOM: Unicode, little endian - // Skip the BOM when creating the reader - Debug.Assert(*src == 0xFEFF); - - return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); - } - } - - internal sealed class TemporaryStreamStorage : IDisposable - { - private readonly TemporaryStorageService _service; - private MemoryMappedInfo? _memoryMappedInfo; - - public TemporaryStreamStorage(TemporaryStorageService service) - => _service = service; - - public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long offset, long size) - { - _service = service; - _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); - } - - public string Name => _memoryMappedInfo!.Name; - public long Offset => _memoryMappedInfo!.Offset; - public long Size => _memoryMappedInfo!.Size; - - public void Dispose() - { - // Destructors of SafeHandle and FileStream in MemoryMappedFile - // will eventually release resources if this Dispose is not called - // explicitly - _memoryMappedInfo?.Dispose(); - _memoryMappedInfo = null; - } - - public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - - return _memoryMappedInfo.CreateReadableStream(); - } - } - - public void WriteStream(Stream stream, CancellationToken cancellationToken) - { - if (_memoryMappedInfo != null) - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) - { - var size = stream.Length; - _memoryMappedInfo = _service.CreateTemporaryStorage(size); - using var viewStream = _memoryMappedInfo.CreateWritableStream(); - - using var pooledObject = SharedPools.ByteArray.GetPooledObject(); - var buffer = pooledObject.Object; - while (true) - { - var count = stream.Read(buffer, 0, buffer.Length); - if (count == 0) - break; - - viewStream.Write(buffer, 0, count); - } - } - } - } -} - -internal unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength -{ - private char* _position; - private readonly char* _end; - - public DirectMemoryAccessStreamReader(char* src, int length) - : base(length) - { - RoslynDebug.Assert(src != null); - RoslynDebug.Assert(length >= 0); - - _position = src; - _end = _position + length; - } - - public override int Peek() - { - if (_position >= _end) - { - return -1; - } - - return *_position; - } - - public override int Read() - { - if (_position >= _end) - { - return -1; - } - - return *_position++; - } - - public override int Read(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (index < 0 || index >= buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (count < 0 || (index + count) > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - count = Math.Min(count, (int)(_end - _position)); - if (count > 0) - { - Marshal.Copy((IntPtr)_position, buffer, index, count); - _position += count; - } - - return count; - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 966a904322b92..ce8f883f0e12a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -259,8 +259,9 @@ public readonly SkeletonReferenceCache Clone() // Now read the data back from the stream from the memory mapped file. This will come back as an // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. - return AssemblyMetadata.CreateFromStream( + var result = AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); + GC.KeepAlive(handle); } if (logger != null) From 4b76b6a9b3790747ddfe759c3a29d652bd48679a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 15:21:41 -0700 Subject: [PATCH 179/292] fix --- .../Solution/SolutionCompilationState.SkeletonReferenceCache.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index ce8f883f0e12a..dafd5f5e383c7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -262,6 +262,7 @@ public readonly SkeletonReferenceCache Clone() var result = AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); GC.KeepAlive(handle); + return result; } if (logger != null) From 4aebf85ac3946085ea364fcc3240f33b393feb24 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 20 Apr 2024 16:32:00 -0700 Subject: [PATCH 180/292] Rename --- .../VisualStudioMetadataReferenceManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 0ef1f97083953..ffc07f78fd511 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -91,9 +91,9 @@ public void Dispose() var key = new FileKey(fullPath, snapshotTimestamp); // check existing metadata if (_metadataCache.TryGetMetadata(key, out var source) && - s_metadataToStorageHandles.TryGetValue(source, out var handler)) + s_metadataToStorageHandles.TryGetValue(source, out var handles)) { - return handler; + return handles; } return null; From f96e1e72f990621853654cffa104782cf24e15d2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 00:24:14 -0700 Subject: [PATCH 181/292] Run real memory-mapped-file code during tests --- .../Serialization/SerializerService.cs | 9 +- .../SerializerService_Reference.cs | 116 ++---------------- .../TestTemporaryStorageServiceFactory.cs | 24 ---- .../SolutionTests/SolutionTestHelpers.cs | 8 +- .../CoreTest/SyntaxReferenceTests.cs | 6 +- .../CoreTest/TestCompositionTests.cs | 8 +- 6 files changed, 23 insertions(+), 148 deletions(-) delete mode 100644 src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 02868db1077ea..1c6dd05f655e3 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -6,16 +6,19 @@ using System.Collections.Concurrent; using System.Composition; using System.Linq; +using System.Runtime.Versioning; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Serialization; +#if NETCOREAPP +[SupportedOSPlatform("windows")] +#endif internal partial class SerializerService : ISerializerService { [ExportWorkspaceServiceFactory(typeof(ISerializerService), layer: ServiceLayer.Default), Shared] @@ -36,7 +39,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) private readonly SolutionServices _workspaceServices; - private readonly ITemporaryStorageServiceInternal _storageService; + private readonly TemporaryStorageService _storageService; private readonly ITextFactoryService _textService; private readonly IDocumentationProviderService? _documentationService; private readonly IAnalyzerAssemblyLoaderProvider _analyzerLoaderProvider; @@ -48,7 +51,7 @@ private protected SerializerService(SolutionServices workspaceServices) { _workspaceServices = workspaceServices; - _storageService = workspaceServices.GetRequiredService(); + _storageService = (TemporaryStorageService)workspaceServices.GetRequiredService(); _textService = workspaceServices.GetRequiredService(); _analyzerLoaderProvider = workspaceServices.GetRequiredService(); _documentationService = workspaceServices.GetService(); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index c5e19134e2309..f2a912bbf53ec 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -359,32 +359,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT } var metadataKind = (MetadataImageKind)imageKind; - if (_storageService == null) - { - if (metadataKind == MetadataImageKind.Assembly) - { - using var pooledMetadata = Creator.CreateList(); - - var count = reader.ReadInt32(); - for (var i = 0; i < count; i++) - { - metadataKind = (MetadataImageKind)reader.ReadInt32(); - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); - -#pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - pooledMetadata.Object.Add(ReadModuleMetadataFrom(reader, kind)); -#pragma warning restore CA2016 - } - - return (AssemblyMetadata.Create(pooledMetadata.Object), storages: default); - } - - Contract.ThrowIfFalse(metadataKind == MetadataImageKind.Module); -#pragma warning disable CA2016 // https://github.com/dotnet/roslyn-analyzers/issues/4985 - return (ReadModuleMetadataFrom(reader, kind), storages: default); -#pragma warning restore CA2016 - } - if (metadataKind == MetadataImageKind.Assembly) { using var pooledMetadata = Creator.CreateList(); @@ -421,25 +395,18 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var storageStream = storage.ReadStream(cancellationToken); Contract.ThrowIfFalse(length == storageStream.Length); - var metadata = GetMetadata(storageStream, length); - - return (metadata, storage); - } - - private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, SerializationKinds kind) - { - Contract.ThrowIfFalse(SerializationKinds.Bits == kind); - - var array = reader.ReadByteArray(); - var pinnedObject = new PinnedObject(array); - - // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in - // as the onDispose callback of the metadata. - return ModuleMetadata.CreateFromMetadata( - pinnedObject.GetPointer(), array.Length, pinnedObject.Dispose); + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as + // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of + // the metadata. + unsafe + { + var metadata = ModuleMetadata.CreateFromMetadata( + (IntPtr)storageStream.PositionPointer, (int)storageStream.Length, storageStream.Dispose); + return (metadata, storage); + } } - private (ITemporaryStreamStorageInternal storage, long length) GetTemporaryStorage( + private (TemporaryStorageService.TemporaryStreamStorage storage, long length) GetTemporaryStorage( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); @@ -475,40 +442,6 @@ private static ModuleMetadata ReadModuleMetadataFrom(ObjectReader reader, Serial } } - private static ModuleMetadata GetMetadata(Stream stream, long length) - { - if (stream is UnmanagedMemoryStream unmanagedStream) - { - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as - // long as the ModuleMetadata is alive due to passing its .Dispose method in as the onDispose callback of - // the metadata. - unsafe - { - return ModuleMetadata.CreateFromMetadata( - (IntPtr)unmanagedStream.PositionPointer, (int)unmanagedStream.Length, unmanagedStream.Dispose); - } - } - - PinnedObject pinnedObject; - if (stream is MemoryStream memory && - memory.TryGetBuffer(out var buffer) && - buffer.Offset == 0) - { - pinnedObject = new PinnedObject(buffer.Array!); - } - else - { - var array = new byte[length]; - stream.Read(array, 0, (int)length); - pinnedObject = new PinnedObject(array); - } - - // PinnedObject will be kept alive as long as the ModuleMetadata is alive due to passing its .Dispose method in - // as the onDispose callback of the metadata. - return ModuleMetadata.CreateFromMetadata( - pinnedObject.GetPointer(), (int)length, pinnedObject.Dispose); - } - private static void CopyByteArrayToStream(ObjectReader reader, Stream stream, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -553,35 +486,6 @@ private static void WriteUnresolvedAnalyzerReferenceTo(AnalyzerReference referen } } - private sealed class PinnedObject : IDisposable - { - // shouldn't be read-only since GCHandle is a mutable struct - private GCHandle _gcHandle; - - public PinnedObject(byte[] array) - => _gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned); - - internal IntPtr GetPointer() - => _gcHandle.AddrOfPinnedObject(); - - private void OnDispose() - { - if (_gcHandle.IsAllocated) - { - _gcHandle.Free(); - } - } - - ~PinnedObject() - => OnDispose(); - - public void Dispose() - { - GC.SuppressFinalize(this); - OnDispose(); - } - } - private sealed class MissingMetadataReference : PortableExecutableReference { private readonly DocumentationProvider _provider; diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs deleted file mode 100644 index 21147f43736bd..0000000000000 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestTemporaryStorageServiceFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.UnitTests.Persistence -{ - [ExportWorkspaceServiceFactory(typeof(ITemporaryStorageServiceInternal), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal sealed class TestTemporaryStorageServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestTemporaryStorageServiceFactory() - { - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => TrivialTemporaryStorageService.Instance; - } -} diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs index 0cdc9229c893f..adab4802f8a7b 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests @@ -18,16 +17,13 @@ public static Workspace CreateWorkspace(Type[]? additionalParts = null, TestHost => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).WithTestHostParts(testHost).GetHostServices()); public static Workspace CreateWorkspaceWithNormalText() - => CreateWorkspace( - [ - typeof(TestTemporaryStorageServiceFactory) - ]); + => CreateWorkspace(); public static Workspace CreateWorkspaceWithRecoverableText() => CreateWorkspace(); public static Workspace CreateWorkspaceWithPartialSemantics(TestHost testHost = TestHost.InProcess) - => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics([typeof(TestTemporaryStorageServiceFactory)], testHost); + => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics(testHost: testHost); #nullable disable diff --git a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs index 2ce0a35a4bb70..be1d4ab59b970 100644 --- a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs +++ b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Roslyn.Test.Utilities; using Xunit; using CS = Microsoft.CodeAnalysis.CSharp; @@ -26,10 +25,7 @@ private static Workspace CreateWorkspace(Type[] additionalParts = null) => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).GetHostServices()); private static Workspace CreateWorkspaceWithRecoverableSyntaxTrees() - => CreateWorkspace( - [ - typeof(TestTemporaryStorageServiceFactory) - ]); + => CreateWorkspace(); private static Solution AddSingleFileCSharpProject(Solution solution, string source) { diff --git a/src/Workspaces/CoreTest/TestCompositionTests.cs b/src/Workspaces/CoreTest/TestCompositionTests.cs index 13237a0dcb699..24535fa007888 100644 --- a/src/Workspaces/CoreTest/TestCompositionTests.cs +++ b/src/Workspaces/CoreTest/TestCompositionTests.cs @@ -2,21 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.UnitTests.Persistence; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests { + using static SourceGeneratorTelemetryCollectorWorkspaceServiceTests; + public class TestCompositionTests { [Fact] public void FactoryReuse() { - var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestErrorReportingService), typeof(TestTemporaryStorageServiceFactory)); - var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestTemporaryStorageServiceFactory), typeof(TestErrorReportingService)); + var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestErrorReportingService), typeof(TestSourceGeneratorTelemetryCollectorWorkspaceServiceFactory)); + var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestSourceGeneratorTelemetryCollectorWorkspaceServiceFactory), typeof(TestErrorReportingService)); Assert.Same(composition1.ExportProviderFactory, composition2.ExportProviderFactory); } From 179d0584222cb38e07a45598175d88578b33b6d2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 00:26:04 -0700 Subject: [PATCH 182/292] docs --- src/Workspaces/Core/Portable/Serialization/SerializerService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 1c6dd05f655e3..52125801cf770 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -51,6 +51,8 @@ private protected SerializerService(SolutionServices workspaceServices) { _workspaceServices = workspaceServices; + // Serialization is only involved when we have a remote process. Which is only in VS. So the type of the + // storage service here is well known. _storageService = (TemporaryStorageService)workspaceServices.GetRequiredService(); _textService = workspaceServices.GetRequiredService(); _analyzerLoaderProvider = workspaceServices.GetRequiredService(); From 1cecae23aae8fc20a769476dc68af27bd60ced25 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 00:35:18 -0700 Subject: [PATCH 183/292] tweak --- src/Workspaces/Core/Portable/Serialization/SerializerService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs index 52125801cf770..e786280b8b51c 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService.cs @@ -51,7 +51,7 @@ private protected SerializerService(SolutionServices workspaceServices) { _workspaceServices = workspaceServices; - // Serialization is only involved when we have a remote process. Which is only in VS. So the type of the + // Serialization is only involved when we have a remote process. Which is only in VS. So the type of the // storage service here is well known. _storageService = (TemporaryStorageService)workspaceServices.GetRequiredService(); _textService = workspaceServices.GetRequiredService(); From 3a072904e3032953d554ccecf9857b7721d7076f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 09:39:25 -0700 Subject: [PATCH 184/292] test warning' --- .../CoreTestUtilities/Remote/TestSerializerService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs index bf86ae8934251..5d8152928777a 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/TestSerializerService.cs @@ -19,6 +19,8 @@ using Roslyn.Utilities; using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; +#pragma warning disable CA1416 // Validate platform compatibility + namespace Microsoft.CodeAnalysis.UnitTests.Remote { internal sealed class TestSerializerService : SerializerService From cd2fa5c7e851b88985159449286db9fa1922e069 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 09:41:58 -0700 Subject: [PATCH 185/292] usings --- .../Core/Portable/Serialization/SerializerService_Reference.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index f2a912bbf53ec..d73abc3722bb8 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -7,10 +7,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.IO; -using System.Linq; using System.Reflection.Metadata; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; From a267879e773fb2f4a023583ea1b17ca5f553d97e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 09:54:37 -0700 Subject: [PATCH 186/292] Remove unneeded tests methods --- .../CoreTest/SolutionTests/SolutionTestHelpers.cs | 6 ------ src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs index adab4802f8a7b..fa04089a158a9 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTestHelpers.cs @@ -16,12 +16,6 @@ internal static class SolutionTestHelpers public static Workspace CreateWorkspace(Type[]? additionalParts = null, TestHost testHost = TestHost.InProcess) => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).WithTestHostParts(testHost).GetHostServices()); - public static Workspace CreateWorkspaceWithNormalText() - => CreateWorkspace(); - - public static Workspace CreateWorkspaceWithRecoverableText() - => CreateWorkspace(); - public static Workspace CreateWorkspaceWithPartialSemantics(TestHost testHost = TestHost.InProcess) => WorkspaceTestUtilities.CreateWorkspaceWithPartialSemantics(testHost: testHost); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 6f023828ec35f..e1ac87c0b8243 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -2651,7 +2651,7 @@ public void TestDocumentChangedOnDiskIsNotObserved() var file = Temp.CreateFile().WriteAllText(text1, Encoding.UTF8); // create a solution that evicts from the cache immediately. - using var workspace = CreateWorkspaceWithRecoverableText(); + using var workspace = CreateWorkspace(); var sol = workspace.CurrentSolution; var pid = ProjectId.CreateNewId(); From bb311d59fcc6068cea970d78371f6366165cb00f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 10:01:00 -0700 Subject: [PATCH 187/292] Fixup tests --- .../CoreTest/SyntaxReferenceTests.cs | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs index be1d4ab59b970..6614d2e7f9b26 100644 --- a/src/Workspaces/CoreTest/SyntaxReferenceTests.cs +++ b/src/Workspaces/CoreTest/SyntaxReferenceTests.cs @@ -2,30 +2,26 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Linq; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; +using Microsoft.CodeAnalysis.Shared.Extensions; using CS = Microsoft.CodeAnalysis.CSharp; using VB = Microsoft.CodeAnalysis.VisualBasic; +using System.Threading.Tasks; +using System.Threading; namespace Microsoft.CodeAnalysis.UnitTests { [UseExportProvider] [Trait(Traits.Feature, Traits.Features.Workspace)] - public class SyntaxReferenceTests : TestBase + public sealed class SyntaxReferenceTests : TestBase { - private static Workspace CreateWorkspace(Type[] additionalParts = null) - => new AdhocWorkspace(FeaturesTestCompositions.Features.AddParts(additionalParts).GetHostServices()); - - private static Workspace CreateWorkspaceWithRecoverableSyntaxTrees() - => CreateWorkspace(); + private static Workspace CreateWorkspace() + => new AdhocWorkspace(FeaturesTestCompositions.Features.GetHostServices()); private static Solution AddSingleFileCSharpProject(Solution solution, string source) { @@ -48,16 +44,16 @@ private static Solution AddSingleFileVisualBasicProject(Solution solution, strin } [Fact] - public void TestCSharpReferenceToZeroWidthNode() + public async Task TestCSharpReferenceToZeroWidthNode() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileCSharpProject(workspace.CurrentSolution, @" public class C<> { } "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // this is an expected TypeParameterSyntax with a missing identifier token (it is zero-length w/ an error attached to it) var node = tree.GetRoot().DescendantNodes().OfType().Single(); @@ -71,15 +67,15 @@ public class C<> } [Fact] - public void TestVisualBasicReferenceToZeroWidthNode() + public async Task TestVisualBasicReferenceToZeroWidthNode() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileVisualBasicProject(workspace.CurrentSolution, @" Public Class C(Of ) End Class "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // this is an expected TypeParameterSyntax with a missing identifier token (it is zero-length w/ an error attached to it) var node = tree.GetRoot().DescendantNodes().OfType().Single(); @@ -93,9 +89,9 @@ End Class } [Fact] - public void TestCSharpReferenceToNodeInStructuredTrivia() + public async Task TestCSharpReferenceToNodeInStructuredTrivia() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileCSharpProject(workspace.CurrentSolution, @" #if true || true public class C @@ -103,7 +99,7 @@ public class C } #endif "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var node = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -116,9 +112,9 @@ public class C } [Fact] - public void TestVisualBasicReferenceToNodeInStructuredTrivia() + public async Task TestVisualBasicReferenceToNodeInStructuredTrivia() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileVisualBasicProject(workspace.CurrentSolution, @" #If True Or True Then Public Class C @@ -126,7 +122,7 @@ End Class #End If "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var node = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -139,9 +135,9 @@ End Class } [Fact] - public void TestCSharpReferenceToZeroWidthNodeInStructuredTrivia() + public async Task TestCSharpReferenceToZeroWidthNodeInStructuredTrivia() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileCSharpProject(workspace.CurrentSolution, @" #if true || public class C @@ -150,7 +146,7 @@ public class C #endif "); - var tree = solution.Projects.First().Documents.First().GetSyntaxTreeAsync().Result; + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var binary = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); @@ -169,7 +165,7 @@ public class C [Fact] public async System.Threading.Tasks.Task TestVisualBasicReferenceToZeroWidthNodeInStructuredTriviaAsync() { - using var workspace = CreateWorkspaceWithRecoverableSyntaxTrees(); + using var workspace = CreateWorkspace(); var solution = AddSingleFileVisualBasicProject(workspace.CurrentSolution, @" #If (True Or ) Then Public Class C @@ -177,7 +173,7 @@ End Class #End If "); - var tree = await solution.Projects.First().Documents.First().GetSyntaxTreeAsync(); + var tree = await solution.Projects.First().Documents.First().GetRequiredSyntaxTreeAsync(CancellationToken.None); // find binary node that is part of #if directive var binary = tree.GetRoot().DescendantNodes(descendIntoTrivia: true).OfType().First(); From 00826916c936f57fadbd00c3b65cd3699652e3b4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 11:45:22 -0700 Subject: [PATCH 188/292] Simplify --- ...emporaryStorageService.MemoryMappedInfo.cs | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs index 0bd1fd88552e7..ba3b7a37ec0c6 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -178,35 +178,20 @@ public void Dispose() _memoryMappedFile.Dispose(); } - private sealed unsafe class MemoryMappedViewUnmanagedMemoryStream : UnmanagedMemoryStream + private sealed unsafe class MemoryMappedViewUnmanagedMemoryStream( + ReferenceCountedDisposable accessor, + long length) : UnmanagedMemoryStream( + (byte*)accessor.Target.SafeMemoryMappedViewHandle.DangerousGetHandle() + accessor.Target.PointerOffset, + length) { - private readonly ReferenceCountedDisposable _accessor; - private byte* _start; - - public MemoryMappedViewUnmanagedMemoryStream(ReferenceCountedDisposable accessor, long length) - : base((byte*)accessor.Target.SafeMemoryMappedViewHandle.DangerousGetHandle() + accessor.Target.PointerOffset, length) - { - _accessor = accessor; - _start = this.PositionPointer; - } + private readonly ReferenceCountedDisposable _accessor = accessor; protected override void Dispose(bool disposing) { base.Dispose(disposing); - if (disposing) - { _accessor.Dispose(); - } - - _start = null; } - - /// - /// Get underlying native memory directly. - /// - public IntPtr GetPointer() - => (IntPtr)_start; } } } From 2bef2b967e4f9aeccd99359754171e7f65e80da4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 11:51:40 -0700 Subject: [PATCH 189/292] Add assert --- .../Core/Portable/Serialization/SerializerService_Reference.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 6bf545a2707ff..abda6f7a06a2e 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -397,6 +397,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); + Contract.ThrowIfTrue(length != storageHandle.Identifier.Size); return ReadModuleMetadataFromStorage(storageHandle); } From d69c434f3f520b27c8f7cb8493341f5653d365e4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 11:55:58 -0700 Subject: [PATCH 190/292] Simplify api --- .../Portable/Serialization/SerializerService_Reference.cs | 2 -- .../Portable/TemporaryStorage/TemporaryStorageService.cs | 1 + .../Host/TemporaryStorage/ITemporaryStorageService.cs | 5 ++++- .../Host/TemporaryStorage/TrivialTemporaryStorageService.cs | 1 + .../ProjectSystem/ProjectSystemProjectOptionsProcessor.cs | 2 -- .../SolutionCompilationState.SkeletonReferenceCache.cs | 1 - .../WorkspaceServiceTests/TemporaryStorageServiceTests.cs | 5 ----- 7 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index abda6f7a06a2e..49b572c70c606 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -394,8 +394,6 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT CopyByteArrayToStream(reader, stream, cancellationToken); var length = stream.Length; - - stream.Position = 0; var storageHandle = _storageService.WriteToTemporaryStorage(stream, cancellationToken); Contract.ThrowIfTrue(length != storageHandle.Identifier.Size); return ReadModuleMetadataFromStorage(storageHandle); diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index b3fd55389ce4c..d8bce5619af03 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -103,6 +103,7 @@ public TemporaryTextStorage AttachTemporaryTextStorage( public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { + stream.Position = 0; var storage = new TemporaryStreamStorage(this); storage.WriteStream(stream, cancellationToken); var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 26efdb3f3417c..adc0a016316a6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -23,7 +23,7 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// /// Write the provided to a new memory-mapped-file. Returns a handle to the data that can /// be used to identify the data across processes allowing it to be read back in in any process. Note: the data - /// will not longer be readonable if the returned is disposed. + /// will not longer be readable if the returned is disposed. /// /// /// This type is used for two purposes. @@ -41,6 +41,9 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// when we get the next large compiler command line. /// /// + /// Note: The stream provided mus support . The stream will also be reset to 0 within this method. The caller does not need to reset the stream + /// itself. /// TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index de638eb1272de..738cbef3f6f72 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -28,6 +28,7 @@ public ITemporaryTextStorageInternal CreateTemporaryTextStorage() public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { + stream.Position = 0; var storage = new StreamStorage(); storage.WriteStream(stream); var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), 0, 0); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index b2ea809fa6420..469d8ec96427e 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -85,8 +85,6 @@ private bool ReparseCommandLineIfChanged_NoLock(ImmutableArray arguments writer.WriteLine(value); writer.Flush(); - stream.Position = 0; - _commandLineStorageHandle = _temporaryStorageService.WriteToTemporaryStorage(stream, CancellationToken.None); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index dafd5f5e383c7..a58bb1e08b151 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -254,7 +254,6 @@ public readonly SkeletonReferenceCache Clone() // Then, dump that in-memory-stream to a memory-mapped file. Doing this allows us to have the // assembly-metadata point directly to that pointer in memory, instead of it having to make its // own copy it needs to own the lifetime of. - stream.Position = 0; var handle = temporaryStorageService.WriteToTemporaryStorage(stream, cancellationToken); // Now read the data back from the stream from the memory mapped file. This will come back as an diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 9da9edc7d6aa8..60a50fa11da93 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -58,7 +58,6 @@ public void TestTemporaryStorageStream() data.WriteByte((byte)(i % 2)); } - data.Position = 0; var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); using var result = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); @@ -191,7 +190,6 @@ public void TestTemporaryStorageScaling() var storageHandles = new List(fileCount); for (var i = 0; i < fileCount; i++) { - data.Position = 0; var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); storageHandles.Add(handle); } @@ -218,7 +216,6 @@ public void StreamTest1() expected.WriteByte((byte)(i % byte.MaxValue)); } - expected.Position = 0; var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; @@ -244,7 +241,6 @@ public void StreamTest2() expected.WriteByte((byte)(i % byte.MaxValue)); } - expected.Position = 0; var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; @@ -285,7 +281,6 @@ public void StreamTest3() expected.WriteByte(value); } - expected.Position = 0; var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; From cd8beb3b656042fab15a0671acebfef2649be689 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 18:50:52 -0700 Subject: [PATCH 191/292] Remove dispose capability --- ...emporaryStorageService.MemoryMappedInfo.cs | 29 +++-------------- .../TemporaryStorageServiceFactory.cs | 31 ++++++------------- .../TemporaryStorage/ITemporaryStorage.cs | 2 +- .../ProjectSystemProjectOptionsProcessor.cs | 2 -- .../TemporaryStorageServiceTests.cs | 8 ++--- 5 files changed, 20 insertions(+), 52 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs index 0bd1fd88552e7..88dcf081ba7b2 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -22,11 +22,6 @@ internal partial class TemporaryStorageService /// metadata dll shadow copy. shared view will help those cases. /// /// - /// Instances of this class should be disposed when they are no longer needed. After disposing this - /// instance, it should no longer be used. However, streams obtained through - /// or will not be invalidated until they are disposed independently (which - /// may occur before or after the is disposed. - /// /// This class and its nested types have familiar APIs and predictable behavior when used in other code, /// but are non-trivial to work on. The implementations of adhere to the best /// practices described in @@ -34,7 +29,7 @@ internal partial class TemporaryStorageService /// Update: Dispose, Finalization, and Resource Management. Additional notes regarding operating system /// behavior leveraged for efficiency are given in comments. /// - internal sealed class MemoryMappedInfo(ReferenceCountedDisposable memoryMappedFile, string name, long offset, long size) : IDisposable + internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string name, long offset, long size) : IDisposable { /// /// The memory mapped file. @@ -44,7 +39,7 @@ internal sealed class MemoryMappedInfo(ReferenceCountedDisposable /// - private readonly ReferenceCountedDisposable _memoryMappedFile = memoryMappedFile; + private readonly MemoryMappedFile _memoryMappedFile = memoryMappedFile; /// /// A weak reference to a read-only view for the memory mapped file. @@ -62,7 +57,7 @@ internal sealed class MemoryMappedInfo(ReferenceCountedDisposable.WeakReference _weakReadAccessor; public MemoryMappedInfo(string name, long offset, long size) - : this(new ReferenceCountedDisposable(MemoryMappedFile.OpenExisting(name)), name, offset, size) + : this(MemoryMappedFile.OpenExisting(name), name, offset, size) { } @@ -96,14 +91,7 @@ public UnmanagedMemoryStream CreateReadableStream() if (streamAccessor == null) { var rawAccessor = RunWithCompactingGCFallback( - static info => - { - using var memoryMappedFile = info._memoryMappedFile.TryAddReference(); - if (memoryMappedFile is null) - throw new ObjectDisposedException(typeof(MemoryMappedInfo).FullName); - - return memoryMappedFile.Target.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read); - }, + static info => info._memoryMappedFile.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read), this); streamAccessor = new ReferenceCountedDisposable(rawAccessor); _weakReadAccessor = new ReferenceCountedDisposable.WeakReference(streamAccessor); @@ -120,14 +108,7 @@ public UnmanagedMemoryStream CreateReadableStream() public Stream CreateWritableStream() { return RunWithCompactingGCFallback( - static info => - { - using var memoryMappedFile = info._memoryMappedFile.TryAddReference(); - if (memoryMappedFile is null) - throw new ObjectDisposedException(typeof(MemoryMappedInfo).FullName); - - return memoryMappedFile.Target.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write); - }, + static info => info._memoryMappedFile.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write), this); } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index 4fa4e5cf53697..c58bd609f8619 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -36,7 +36,7 @@ internal sealed partial class TemporaryStorageService : ITemporaryStorageService /// The value of 256k reduced the number of files dumped to separate memory mapped files by 60% compared to /// the next lower power-of-2 size for Roslyn.sln itself. /// - /// + /// private const long SingleFileThreshold = 256 * 1024; /// @@ -47,7 +47,7 @@ internal sealed partial class TemporaryStorageService : ITemporaryStorageService /// Roslyn.sln a snapshot. This keeps the data safe, so that we can drop it from memory when not needed, but /// reconstitute the contents we originally had in the snapshot in case the original files change on disk. /// - /// + /// private const long MultiFileBlockSize = SingleFileThreshold * 32; private readonly IWorkspaceThreadingService? _workspaceThreadingService; @@ -68,23 +68,23 @@ internal sealed partial class TemporaryStorageService : ITemporaryStorageService /// The most recent memory mapped file for creating multiple storage units. It will be used via bump-pointer /// allocation until space is no longer available in it. Access should be synchronized on /// - private ReferenceCountedDisposable.WeakReference _weakFileReference; + private MemoryMappedFile? _fileReference; /// The name of the current memory mapped file for multiple storage units. Access should be synchronized on /// - /// + /// private string? _name; /// The total size of the current memory mapped file for multiple storage units. Access should be /// synchronized on - /// + /// private long _fileSize; /// /// The offset into the current memory mapped file where the next storage unit can be held. Access should be /// synchronized on . /// - /// + /// private long _offset; [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] @@ -125,8 +125,7 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) { // Larger blocks are allocated separately var mapName = CreateUniqueName(size); - var storage = MemoryMappedFile.CreateNew(mapName, size); - return new MemoryMappedInfo(new ReferenceCountedDisposable(storage), mapName, offset: 0, size: size); + return new MemoryMappedInfo(mapName, offset: 0, size: size); } lock (_gate) @@ -134,14 +133,13 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) // Obtain a reference to the memory mapped file, creating one if necessary. If a reference counted // handle to a memory mapped file is obtained in this section, it must either be disposed before // returning or returned to the caller who will own it through the MemoryMappedInfo. - var reference = _weakFileReference.TryAddReference(); + var reference = _fileReference; if (reference == null || _offset + size > _fileSize) { var mapName = CreateUniqueName(MultiFileBlockSize); - var file = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize); - reference = new ReferenceCountedDisposable(file); - _weakFileReference = new ReferenceCountedDisposable.WeakReference(reference); + reference = MemoryMappedFile.CreateNew(mapName, MultiFileBlockSize); + _fileReference = reference; _name = mapName; _fileSize = MultiFileBlockSize; _offset = size; @@ -330,15 +328,6 @@ public TemporaryStreamStorage(TemporaryStorageService service, string storageNam public long Offset => _memoryMappedInfo!.Offset; public long Size => _memoryMappedInfo!.Size; - public void Dispose() - { - // Destructors of SafeHandle and FileStream in MemoryMappedFile - // will eventually release resources if this Dispose is not called - // explicitly - _memoryMappedInfo?.Dispose(); - _memoryMappedInfo = null; - } - Stream ITemporaryStreamStorageInternal.ReadStream(CancellationToken cancellationToken) => ReadStream(cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index ef647f1af7b36..8a3424a5cce7b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -41,7 +41,7 @@ internal interface ITemporaryTextStorageInternal : IDisposable Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); } -internal interface ITemporaryStreamStorageInternal : IDisposable +internal interface ITemporaryStreamStorageInternal { Stream ReadStream(CancellationToken cancellationToken = default); void WriteStream(Stream stream, CancellationToken cancellationToken = default); diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 11be0ef802128..626d14c0be186 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -71,7 +71,6 @@ private bool ReparseCommandLineIfChanged_NoLock(ImmutableArray arguments // Dispose the existing stored command-line and then persist the new one so we can // recover it later. Only bother persisting things if we have a non-empty string. - _commandLineStorage?.Dispose(); _commandLineStorage = null; if (!arguments.IsEmpty) { @@ -276,7 +275,6 @@ public void Dispose() lock (_gate) { DisposeOfRuleSetFile_NoLock(); - _commandLineStorage?.Dispose(); } } } diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index df9924ce8f49a..0c810774c1730 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -168,9 +168,10 @@ public void TestTemporaryStorageMemoryMappedFileManagement() { for (var j = 1; j < 5; j++) { - using ITemporaryStreamStorageInternal storage1 = service.CreateTemporaryStreamStorage(), - storage2 = service.CreateTemporaryStreamStorage(); - var storage3 = service.CreateTemporaryStreamStorage(); // let the finalizer run for this instance + ITemporaryStreamStorageInternal + storage1 = service.CreateTemporaryStreamStorage(), + storage2 = service.CreateTemporaryStreamStorage(), + storage3 = service.CreateTemporaryStreamStorage(); storage1.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1)); storage2.WriteStream(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i)); @@ -225,7 +226,6 @@ public void TestTemporaryStorageScaling() { using var s = storageHandles[i].ReadStream(CancellationToken.None); Assert.Equal(1, s.ReadByte()); - storageHandles[i].Dispose(); } } } From 523837395ccf4842d91b8856af2b007b6db82ce5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 19:19:21 -0700 Subject: [PATCH 192/292] Simplify --- ...emporaryStorageService.MemoryMappedInfo.cs | 68 +++++-------------- 1 file changed, 18 insertions(+), 50 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs index 88dcf081ba7b2..f71ceccc95161 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -7,7 +7,6 @@ using System.IO; using System.IO.MemoryMappedFiles; using System.Runtime; -using System.Runtime.InteropServices; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -22,35 +21,25 @@ internal partial class TemporaryStorageService /// metadata dll shadow copy. shared view will help those cases. /// /// - /// This class and its nested types have familiar APIs and predictable behavior when used in other code, - /// but are non-trivial to work on. The implementations of adhere to the best - /// practices described in - /// DG - /// Update: Dispose, Finalization, and Resource Management. Additional notes regarding operating system - /// behavior leveraged for efficiency are given in comments. + /// This class and its nested types have familiar APIs and predictable behavior when used in other code, but + /// are non-trivial to work on. /// - internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string name, long offset, long size) : IDisposable + internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string name, long offset, long size) { /// /// The memory mapped file. /// - /// - /// It is possible for the file to be disposed prior to the view and/or the streams which use it. - /// However, the operating system does not actually close the views which are in use until the file handles - /// are closed as well, even if the file is disposed first. - /// private readonly MemoryMappedFile _memoryMappedFile = memoryMappedFile; /// /// A weak reference to a read-only view for the memory mapped file. /// /// - /// This holds a weak counted reference to current , which - /// allows additional accessors for the same address space to be obtained up until the point when no - /// external code is using it. When the memory is no longer being used by any objects, the view of the memory mapped file is unmapped, - /// making the process address space it previously claimed available for other purposes. If/when it is - /// needed again, a new view is created. + /// This holds a weak counted reference to current , which allows + /// additional accessors for the same address space to be obtained up until the point when no external code is + /// using it. When the memory is no longer being used by any + /// objects, the view of the memory mapped file is unmapped, making the process address space it previously + /// claimed available for other purposes. If/when it is needed again, a new view is created. /// /// This view is read-only, so it is only used by . /// @@ -84,9 +73,9 @@ public MemoryMappedInfo(string name, long offset, long size) /// public UnmanagedMemoryStream CreateReadableStream() { - // Note: TryAddReference behaves according to its documentation even if the target object has been - // disposed. If it returns non-null, then the object will not be disposed before the returned - // reference is disposed (see comments on _memoryMappedFile and TryAddReference). + // Note: TryAddReference behaves according to its documentation even if the target object has been disposed. + // If it returns non-null, then the object will not be disposed before the returned reference is disposed + // (see comments on _memoryMappedFile and TryAddReference). var streamAccessor = _weakReadAccessor.TryAddReference(); if (streamAccessor == null) { @@ -113,16 +102,15 @@ public Stream CreateWritableStream() } /// - /// Run a function which may fail with an if not enough memory is available to - /// satisfy the request. In this case, a full compacting GC pass is forced and the function is attempted - /// again. + /// Run a function which may fail with an if not enough memory is available to satisfy + /// the request. In this case, a full compacting GC pass is forced and the function is attempted again. /// /// - /// and - /// will use a native - /// memory map, which can't trigger a GC. In this case, we'd otherwise crash with OOM, so we don't care - /// about creating a UI delay with a full forced compacting GC. If it crashes the second try, it means we're - /// legitimately out of resources. + /// and will use a native memory map, + /// which can't trigger a GC. In this case, we'd otherwise crash with OOM, so we don't care about creating a UI + /// delay with a full forced compacting GC. If it crashes the second try, it means we're legitimately out of + /// resources. /// /// The type of argument to pass to the callback. /// The type returned by the function. @@ -152,42 +140,22 @@ private static void ForceCompactingGC() GC.Collect(); } - public void Dispose() - { - // See remarks on field for relation between _memoryMappedFile and the views/streams. There is no - // need to write _weakReadAccessor here since lifetime of the target is not owned by this instance. - _memoryMappedFile.Dispose(); - } - private sealed unsafe class MemoryMappedViewUnmanagedMemoryStream : UnmanagedMemoryStream { private readonly ReferenceCountedDisposable _accessor; - private byte* _start; public MemoryMappedViewUnmanagedMemoryStream(ReferenceCountedDisposable accessor, long length) : base((byte*)accessor.Target.SafeMemoryMappedViewHandle.DangerousGetHandle() + accessor.Target.PointerOffset, length) { _accessor = accessor; - _start = this.PositionPointer; } protected override void Dispose(bool disposing) { base.Dispose(disposing); - if (disposing) - { _accessor.Dispose(); - } - - _start = null; } - - /// - /// Get underlying native memory directly. - /// - public IntPtr GetPointer() - => (IntPtr)_start; } } } From 96cbdefba41c18d13de6cd1c53b64df654861069 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 19:20:09 -0700 Subject: [PATCH 193/292] Simplify --- .../TemporaryStorageServiceFactory.cs | 12 ------------ .../Host/TemporaryStorage/ITemporaryStorage.cs | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index c58bd609f8619..fe58c3d1671b8 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -209,18 +209,6 @@ public TemporaryTextStorage( /// public ImmutableArray ContentHash => _contentHash; - public void Dispose() - { - // Destructors of SafeHandle and FileStream in MemoryMappedFile - // will eventually release resources if this Dispose is not called - // explicitly - _memoryMappedInfo?.Dispose(); - - _memoryMappedInfo = null; - _encoding = null; - _contentHash = default; - } - public SourceText ReadText(CancellationToken cancellationToken) { if (_memoryMappedInfo == null) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index 8a3424a5cce7b..dc38ed43464a7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -33,7 +33,7 @@ public interface ITemporaryStreamStorage : IDisposable /// /// TemporaryStorage can be used to read and write text to a temporary storage location. /// -internal interface ITemporaryTextStorageInternal : IDisposable +internal interface ITemporaryTextStorageInternal { SourceText ReadText(CancellationToken cancellationToken = default); Task ReadTextAsync(CancellationToken cancellationToken = default); From 9b0b48034953afe2ed849cc403aecb93c40ace7a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 21 Apr 2024 19:26:48 -0700 Subject: [PATCH 194/292] fix --- src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs | 4 ++-- .../WorkspaceServiceTests/TemporaryStorageServiceTests.cs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs index d8fb050b92a0e..aa516e6d36f6e 100644 --- a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs @@ -86,7 +86,7 @@ public async Task TestCreateFromTemporaryStorage() var text = SourceText.From("Hello, World!"); // Create a temporary storage location - using var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); + var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it await temporaryStorage.WriteTextAsync(text); @@ -108,7 +108,7 @@ public async Task TestCreateFromTemporaryStorageWithEncoding() var text = SourceText.From("Hello, World!", Encoding.ASCII); // Create a temporary storage location - using var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); + var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it await temporaryStorage.WriteTextAsync(text); diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 0c810774c1730..f610799684459 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -84,8 +84,6 @@ private static void TestTemporaryStorage(ITemporaryStorageServiceInternal tempor Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); Assert.Equal(text.Encoding, text2.Encoding); - - temporaryStorage.Dispose(); } [ConditionalFact(typeof(WindowsOnly))] From ec833c0ae9dd18308eabdb6587770066079b567c Mon Sep 17 00:00:00 2001 From: David Wengier Date: Mon, 22 Apr 2024 14:30:07 +1000 Subject: [PATCH 195/292] Check UI context before trying to initialize cohosting --- .../ExternalAccess/Razor/Cohost/Constants.cs | 4 +++ .../RazorDynamicRegistrationServiceFactory.cs | 28 ++++++++++++++----- ...t.CodeAnalysis.ExternalAccess.Razor.csproj | 1 + 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs index 1c361a9fc7003..be036e816f43f 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis.LanguageServer; @@ -16,4 +17,7 @@ internal static class Constants public const string RazorLanguageContract = ProtocolConstants.RazorCohostContract; public static readonly ImmutableArray RazorLanguage = ImmutableArray.Create("Razor"); + + // The UI context is provided by Razor, so this going must match the one in https://github.com/dotnet/razor/blob/main/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs + public static readonly Guid RazorCohostingUIContext = new Guid("6d5b86dc-6b8a-483b-ae30-098a3c7d6774"); } diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs index f3f84a2e888e5..18ca571fa1314 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs @@ -9,15 +9,17 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.VisualStudio.Shell; using Newtonsoft.Json; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; [ExportCSharpVisualBasicLspServiceFactory(typeof(RazorDynamicRegistrationService), WellKnownLspServerKinds.AlwaysActiveVSLspServer), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class RazorDynamicRegistrationServiceFactory([Import(AllowDefault = true)] IRazorCohostDynamicRegistrationService? dynamicRegistrationService) : ILspServiceFactory +internal sealed class RazorDynamicRegistrationServiceFactory([Import(AllowDefault = true)] Lazy? dynamicRegistrationService) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { @@ -28,11 +30,11 @@ public ILspService CreateILspService(LspServices lspServices, WellKnownLspServer private class RazorDynamicRegistrationService : ILspService, IOnInitialized { - private readonly IRazorCohostDynamicRegistrationService? _dynamicRegistrationService; - private readonly IClientLanguageServerManager _clientLanguageServerManager; + private readonly Lazy? _dynamicRegistrationService; + private readonly IClientLanguageServerManager? _clientLanguageServerManager; private readonly JsonSerializerSettings _serializerSettings; - public RazorDynamicRegistrationService(IRazorCohostDynamicRegistrationService? dynamicRegistrationService, IClientLanguageServerManager clientLanguageServerManager) + public RazorDynamicRegistrationService(Lazy? dynamicRegistrationService, IClientLanguageServerManager? clientLanguageServerManager) { _dynamicRegistrationService = dynamicRegistrationService; _clientLanguageServerManager = clientLanguageServerManager; @@ -44,17 +46,29 @@ public RazorDynamicRegistrationService(IRazorCohostDynamicRegistrationService? d public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - if (_dynamicRegistrationService is null) + if (_dynamicRegistrationService is null || _clientLanguageServerManager is null) { return Task.CompletedTask; } + var uiContext = UIContext.FromUIContextGuid(Constants.RazorCohostingUIContext); + uiContext.WhenActivated(() => + { + // Not using the cancellation token passed in, as the context could be activated well after LSP server initialization + InitializeRazor(clientCapabilities, context, CancellationToken.None); + }); + + return Task.CompletedTask; + } + + private void InitializeRazor(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) + { // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities, _serializerSettings); - var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(_clientLanguageServerManager); + var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(_clientLanguageServerManager!); var requestContext = new RazorCohostRequestContext(context); - return _dynamicRegistrationService.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken); + _dynamicRegistrationService!.Value.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken).ReportNonFatalErrorAsync(); } } } diff --git a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj index bd93fb921807e..8cbf092883199 100644 --- a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj +++ b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj @@ -49,6 +49,7 @@ + From 26fe42993257c2e2bcc4abb0e6574941e3914f8f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 00:25:10 -0700 Subject: [PATCH 196/292] Fixes --- .../TemporaryStorageService.MemoryMappedInfo.cs | 15 ++++++++------- .../TemporaryStorageServiceFactory.cs | 11 ++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs index f71ceccc95161..04cdc93da906f 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.MemoryMappedInfo.cs @@ -29,7 +29,7 @@ internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string /// /// The memory mapped file. /// - private readonly MemoryMappedFile _memoryMappedFile = memoryMappedFile; + public readonly MemoryMappedFile MemoryMappedFile = memoryMappedFile; /// /// A weak reference to a read-only view for the memory mapped file. @@ -45,10 +45,11 @@ internal sealed class MemoryMappedInfo(MemoryMappedFile memoryMappedFile, string /// private ReferenceCountedDisposable.WeakReference _weakReadAccessor; - public MemoryMappedInfo(string name, long offset, long size) - : this(MemoryMappedFile.OpenExisting(name), name, offset, size) - { - } + public static MemoryMappedInfo OpenExisting(string name, long offset, long size) + => new(MemoryMappedFile.OpenExisting(name), name, offset, size); + + public static MemoryMappedInfo CreateNew(string name, long size) + => new(MemoryMappedFile.CreateNew(name, size), name, offset: 0, size); /// /// The name of the memory mapped file. @@ -80,7 +81,7 @@ public UnmanagedMemoryStream CreateReadableStream() if (streamAccessor == null) { var rawAccessor = RunWithCompactingGCFallback( - static info => info._memoryMappedFile.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read), + static info => info.MemoryMappedFile.CreateViewAccessor(info.Offset, info.Size, MemoryMappedFileAccess.Read), this); streamAccessor = new ReferenceCountedDisposable(rawAccessor); _weakReadAccessor = new ReferenceCountedDisposable.WeakReference(streamAccessor); @@ -97,7 +98,7 @@ public UnmanagedMemoryStream CreateReadableStream() public Stream CreateWritableStream() { return RunWithCompactingGCFallback( - static info => info._memoryMappedFile.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write), + static info => info.MemoryMappedFile.CreateViewStream(info.Offset, info.Size, MemoryMappedFileAccess.Write), this); } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs index fe58c3d1671b8..f4c04c2b06f58 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageServiceFactory.cs @@ -121,12 +121,9 @@ public TemporaryStreamStorage AttachTemporaryStreamStorage(string storageName, l /// A describing the allocated block. private MemoryMappedInfo CreateTemporaryStorage(long size) { + // Larger blocks are allocated separately if (size >= SingleFileThreshold) - { - // Larger blocks are allocated separately - var mapName = CreateUniqueName(size); - return new MemoryMappedInfo(mapName, offset: 0, size: size); - } + return MemoryMappedInfo.CreateNew(CreateUniqueName(size), size: size); lock (_gate) { @@ -182,7 +179,7 @@ public TemporaryTextStorage( _checksumAlgorithm = checksumAlgorithm; _encoding = encoding; _contentHash = contentHash; - _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); + _memoryMappedInfo = MemoryMappedInfo.OpenExisting(storageName, offset, size); } // TODO: cleanup https://github.com/dotnet/roslyn/issues/43037 @@ -307,7 +304,7 @@ public TemporaryStreamStorage(TemporaryStorageService service) public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long offset, long size) { _service = service; - _memoryMappedInfo = new MemoryMappedInfo(storageName, offset, size); + _memoryMappedInfo = MemoryMappedInfo.OpenExisting(storageName, offset, size); } // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 From 7abbaad682e9feb3391c22459b6c367b65f70131 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Mon, 22 Apr 2024 09:22:26 -0700 Subject: [PATCH 197/292] Getting MEF to work at runtime --- .../DiagnosticSourceManager.cs | 20 +++++++++---------- ...cePullDiagnosticsHandler_IOnInitialized.cs | 10 ++++++---- ...stractHotReloadDiagnosticSourceProvider.cs | 2 +- ...kspaceHotReloadDiagnosticSourceProvider.cs | 2 ++ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 1db563fed292b..03321965e977b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -17,33 +17,31 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics [Export(typeof(IDiagnosticSourceManager)), Shared] internal class DiagnosticSourceManager : IDiagnosticSourceManager { - private readonly Lazy> _documentProviders; - private readonly Lazy> _workspaceProviders; - private readonly Lazy> sourceProviders; + private readonly ImmutableDictionary _documentProviders; + private readonly ImmutableDictionary _workspaceProviders; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DiagnosticSourceManager(/*[ImportMany] Lazy> sourceProviders*/) + public DiagnosticSourceManager([ImportMany] IEnumerable sourceProviders) { - sourceProviders = new(() => new List()); - _documentProviders = new(() => sourceProviders.Value + _documentProviders = sourceProviders .Where(p => p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp)); + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); - _workspaceProviders = new(() => sourceProviders.Value + _workspaceProviders = sourceProviders .Where(p => !p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp)); + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); } /// public IEnumerable GetSourceNames(bool isDocument) - => (isDocument ? _documentProviders : _workspaceProviders).Value.Keys; + => (isDocument ? _documentProviders : _workspaceProviders).Keys; /// public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) { var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; - if (providersDictionary.Value.TryGetValue(sourceName, out var provider)) + if (providersDictionary.TryGetValue(sourceName, out var provider)) return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); return new([]); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index 4ee7886ed524e..7d3260b36184a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -14,12 +14,14 @@ internal sealed partial class PublicWorkspacePullDiagnosticsHandler : IOnInitial public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { var sources = _diagnosticSourceManager.GetSourceNames(isDocument: false); + var regParams = new RegistrationParams + { + Registrations = sources.Select(FromSourceName).ToArray() + }; + regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, - @params: new RegistrationParams() - { - Registrations = sources.Select(FromSourceName).ToArray() - }, + @params: regParams, cancellationToken).ConfigureAwait(false); Registration FromSourceName(string sourceName) diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs index cd0a235e52fa5..dcc71a2714646 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; -internal class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider +internal abstract class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider { string IDiagnosticSourceProvider.Name => "HotReloadDiagnostic"; diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs index 742fba39407f5..9648b2902a8c3 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs @@ -24,6 +24,8 @@ internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticMa : AbstractHotReloadDiagnosticSourceProvider , IDiagnosticSourceProvider { + bool IDiagnosticSourceProvider.IsDocument => false; + async ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { if (context.Solution is not Solution solution) From 5a009a7826ebf643ef6a1368a88cba7e8cb7df0f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 09:57:53 -0700 Subject: [PATCH 198/292] Simplify --- .../Host/TemporaryStorage/TemporaryStorageIdentifier.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs index 23b84ece0e996..2457297fca662 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageIdentifier.cs @@ -11,11 +11,8 @@ namespace Microsoft.CodeAnalysis.Host; /// Identifier for a stream of data placed in a segment of temporary storage (generally a memory mapped file). Can be /// used to identify that segment across processes, allowing for efficient sharing of data. /// -[DataContract] internal sealed record TemporaryStorageIdentifier( - [property: DataMember(Order = 0)] string Name, - [property: DataMember(Order = 1)] long Offset, - [property: DataMember(Order = 2)] long Size) + string Name, long Offset, long Size) { public static TemporaryStorageIdentifier ReadFrom(ObjectReader reader) => new( From 26430188b9c0f616ef07f9636272726939cce96c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 10:40:02 -0700 Subject: [PATCH 199/292] spelling --- .../Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index adc0a016316a6..5636c4b941e0b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -41,7 +41,7 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// when we get the next large compiler command line. /// /// - /// Note: The stream provided mus support . The stream will also be reset to . The stream will also be reset to 0 within this method. The caller does not need to reset the stream /// itself. /// From 3af1ac43c9461c9e70b22c85ed87c6284262cf9c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 10:40:41 -0700 Subject: [PATCH 200/292] Tweak identifier --- .../Host/TemporaryStorage/TrivialTemporaryStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 0f0df26c0a8ae..f854e7fd578a1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -31,7 +31,7 @@ public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, Cancellatio stream.Position = 0; var storage = new StreamStorage(); storage.WriteStream(stream); - var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), 0, 0); + var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: stream.Length); var handle = new TemporaryStorageHandle(memoryMappedFile: null, identifier); s_streamStorage.Add(identifier, storage); return handle; From befe5538e51e3c9427b6aff5be420eb05a755672 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 11:01:06 -0700 Subject: [PATCH 201/292] cleanup --- .../VisualStudioMetadataReferenceManager.cs | 1 - .../SolutionCompilationState.SkeletonReferenceCache.cs | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index ffc07f78fd511..6823a8729ce65 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -220,7 +220,6 @@ void GetStorageInfoFromTemporaryStorage( // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageHandle.Identifier, CancellationToken.None); - GC.KeepAlive(storageHandle); // stream size must be same as what metadata reader said the size should be. Contract.ThrowIfFalse(stream.Length == size); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index a58bb1e08b151..d030944c9521e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -260,6 +260,10 @@ public readonly SkeletonReferenceCache Clone() // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. var result = AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); + + // Note: because we are using a memory-mapped file, we need to keep the handle alive during + // the call to ReadFromTemporaryStorageService. Otherwise, the memory-mapped file could be + // released before we read what we need out of it. GC.KeepAlive(handle); return result; } From 31003d06277562438b38e2768b8204faa6d8dc8f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 11:34:57 -0700 Subject: [PATCH 202/292] simplify --- .../ProjectSystem/ProjectSystemProjectOptionsProcessor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 94acb37990bc2..16878f992c9dd 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -261,8 +261,7 @@ static IEnumerable EnumerateLines( using var stream = temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); using var reader = new StreamReader(stream); - string? line; - while ((line = reader.ReadLine()) != null) + while (reader.ReadLine() is string line) yield return line; } } From fbff6716573a75bd2b4bc5bc17811b6cb758a4aa Mon Sep 17 00:00:00 2001 From: Marco Goertz Date: Mon, 22 Apr 2024 12:10:00 -0700 Subject: [PATCH 203/292] Use a small subset of VS JSON converters for VS Code --- .../VSCodeInternalExtensionUtilities.cs | 52 +++++++++++++++++++ .../Protocol/RoslynLanguageServer.cs | 2 + .../Xaml/Internal/ClientCapabilityProvider.cs | 6 +++ 3 files changed, 60 insertions(+) create mode 100644 src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs diff --git a/src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs b/src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs new file mode 100644 index 0000000000000..a1d482e7a480a --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Protocol/Internal/Converters/VSCodeInternalExtensionUtilities.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.LanguageServer.Protocol +{ + using Newtonsoft.Json; + + /// + /// Utilities to aid work with VS Code LSP Extensions. + /// + internal static class VSCodeInternalExtensionUtilities + { + /// + /// Adds necessary to deserialize + /// JSON stream into objects which include VS Code-specific extensions. + /// + /// + /// If is used in parallel to execution of this method, + /// its access needs to be synchronized with this method call, to guarantee that + /// collection is not modified when in use. + /// + /// Instance of which is guaranteed to not work in parallel to this method call. + public static void AddVSCodeInternalExtensionConverters(this JsonSerializer serializer) + { + // Reading the number of converters before we start adding new ones + var existingConvertersCount = serializer.Converters.Count; + + AddOrReplaceConverter(); + AddOrReplaceConverter(); + + void AddOrReplaceConverter() + where TExtension : TBase + { + for (var i = 0; i < existingConvertersCount; i++) + { + var existingConverterType = serializer.Converters[i].GetType(); + if (existingConverterType.IsGenericType && + existingConverterType.GetGenericTypeDefinition() == typeof(VSExtensionConverter<,>) && + existingConverterType.GenericTypeArguments[0] == typeof(TBase)) + { + serializer.Converters.RemoveAt(i); + existingConvertersCount--; + break; + } + } + + serializer.Converters.Add(new VSExtensionConverter()); + } + } + } +} diff --git a/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs b/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs index 3dc9a54510650..f7c38ee329d77 100644 --- a/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs +++ b/src/Features/LanguageServer/Protocol/RoslynLanguageServer.cs @@ -39,6 +39,8 @@ public RoslynLanguageServer( _lspServiceProvider = lspServiceProvider; _serverKind = serverKind; + VSCodeInternalExtensionUtilities.AddVSCodeInternalExtensionConverters(serializer); + // Create services that require base dependencies (jsonrpc) or are more complex to create to the set manually. _baseServices = GetBaseServices(jsonRpc, logger, capabilitiesProvider, hostServices, serverKind, supportedLanguages); diff --git a/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs b/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs index e0f7cf9e77eda..812a79e593eef 100644 --- a/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs +++ b/src/Tools/ExternalAccess/Xaml/Internal/ClientCapabilityProvider.cs @@ -73,6 +73,12 @@ public bool IsDynamicRegistrationSupported(string methodName) return _clientCapabilities?.Workspace?.DidChangeConfiguration?.DynamicRegistration == true; case LSP.Methods.WorkspaceDidChangeWatchedFilesName: return _clientCapabilities?.Workspace?.DidChangeWatchedFiles?.DynamicRegistration == true; + case LSP.VSInternalMethods.OnAutoInsertName: + if (_clientCapabilities.TextDocument is VSInternalTextDocumentClientCapabilities internalTextDocumentClientCapabilities) + { + return internalTextDocumentClientCapabilities.OnAutoInsert?.DynamicRegistration == true; + } + return false; default: throw new InvalidOperationException($"Unsupported dynamic registration method: {methodName}"); } From cd2ec32c121be5b6c8f5faa26bd42938801d725c Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Mon, 22 Apr 2024 13:07:16 -0700 Subject: [PATCH 204/292] Use IDiagnosticsSource to report XAML parse errors --- .../PublicDocumentDiagnosticSourceProvider.cs | 51 +++++++++++++------ .../PublicDocumentPullDiagnosticsHandler.cs | 10 ++-- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 2 +- .../Xaml/External/IXamlDiagnosticSource.cs | 16 ++++++ .../Xaml/Internal/XamlDiagnosticSource.cs | 33 ++++++++++++ .../Internal/XamlDiagnosticSourceProvider.cs | 36 +++++++++++++ .../Xaml/InternalAPI.Unshipped.txt | 6 +++ 7 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs create mode 100644 src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs create mode 100644 src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs index 0880b465d18ae..a992885c06e04 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs @@ -2,33 +2,52 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +using System.Collections.Generic; using System.Collections.Immutable; -using System.Composition; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; -[ExportDiagnosticSourceProvider, Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class PublicDocumentDiagnosticSourceProvider( - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : AbstractDocumentDiagnosticSourceProvider(All) +/// +/// Aggregates multiple source diagnostics +/// +internal sealed class PublicDocumentDiagnosticSource : AbstractDocumentDiagnosticSource { - public const string All = "All_B69807DB-28FB-4846-884A-1152E54C8B62"; + private readonly IDiagnosticSourceManager _diagnosticSourceManager; + private readonly string? _sourceName; - public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + public PublicDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, TextDocument document, string? sourceName) : base(document) { - var textDocument = AbstractDocumentDiagnosticSourceProvider.GetOpenDocument(context); - if (textDocument is null) - return new([]); + _diagnosticSourceManager = diagnosticSourceManager; + _sourceName = sourceName; + } + + public override bool IsLiveSource() => true; - var source = new DocumentDiagnosticSource(diagnosticAnalyzerService, DiagnosticKind.All /* IS THIS RIGHT ???*/, textDocument); - return new([source]); + public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var diagnostics); + var isLive = this.IsLiveSource(); + foreach (var name in GetSourceNames()) + { + var sources = await _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, name, true, cancellationToken).ConfigureAwait(false); + foreach (var source in sources) + { + if (source.IsLiveSource() == isLive) + { + var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); + diagnostics.AddRange(namedDiagnostics); + } + } + } + + return diagnostics.ToImmutableAndClear(); } + + private IEnumerable GetSourceNames() + => string.IsNullOrEmpty(this._sourceName) ? _diagnosticSourceManager.GetSourceNames(isDocument: true) : [_sourceName]; } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 7600bc2f1ee7d..c64cffaf2496d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed // by n DocumentDiagnosticPartialResult literals. -// See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentDiagnosticParams using DocumentDiagnosticPartialReport = SumType; using DocumentDiagnosticReport = SumType; @@ -93,8 +93,12 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi protected override ValueTask> GetOrderedDiagnosticSourcesAsync(DocumentDiagnosticParams diagnosticParams, RequestContext context, CancellationToken cancellationToken) { - if (diagnosticParams.Identifier is string sourceName) - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); + if (context.TextDocument is { } document) + { + // Wrap all sources into ISourceProvider so that we can keep using DocumentDiagnosticReport + // (which supports a single source) + return new([new PublicDocumentDiagnosticSource(_diagnosticSourceManager, document, diagnosticParams.Identifier)]); + } return new([]); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index bc4fa71d8804e..2a52101e93329 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic using DocumentDiagnosticPartialReport = SumType; -internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler, IOnInitialized +internal sealed partial class PublicDocumentPullDiagnosticsHandler// : AbstractDocumentPullDiagnosticHandler, IOnInitialized { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs new file mode 100644 index 0000000000000..0b55c2089007d --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/External/IXamlDiagnosticSource.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +internal interface IXamlDiagnosticSource +{ + Task> GetDiagnosticsAsync( + XamlRequestContext context, + CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs new file mode 100644 index 0000000000000..439572e17b058 --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +internal sealed class XamlDiagnosticSource(IXamlDiagnosticSource xamlDiagnosticSource, TextDocument document) : IDiagnosticSource +{ + Project IDiagnosticSource.GetProject() => document.Project; + ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id); + + TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; + string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {document.FilePath ?? document.Name} in {document.Project.Name}"; + bool IDiagnosticSource.IsLiveSource() => true; + + async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + var xamlRequestContext = XamlRequestContext.FromRequestContext(context); + var diagnostics = await xamlDiagnosticSource.GetDiagnosticsAsync(xamlRequestContext, cancellationToken).ConfigureAwait(false); + var result = diagnostics.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); + return result; + } +} diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..d9b7b043cfe1d --- /dev/null +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; + +[ExportDiagnosticSourceProvider, Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class XamlDiagnosticSourceProvider([Import(AllowDefault = true)] IXamlDiagnosticSource? xamlDiagnosticSource) : IDiagnosticSourceProvider +{ + bool IDiagnosticSourceProvider.IsDocument => true; + + string IDiagnosticSourceProvider.Name => "XamlDiagnosticSource"; + + ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (xamlDiagnosticSource != null && context.TextDocument is { } document && + document.Project.GetAdditionalDocument(document.Id) != null) + { + return new([new XamlDiagnosticSource(xamlDiagnosticSource, document)]); + } + + return new([]); + } +} diff --git a/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt index d62fe5bb29af1..c8d63468defae 100644 --- a/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/Xaml/InternalAPI.Unshipped.txt @@ -37,6 +37,8 @@ Microsoft.CodeAnalysis.ExternalAccess.Xaml.ILocationService.GetSymbolLocationsAs Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService.FromResolveData(object? resolveData) -> (object? data, System.Uri? uri) Microsoft.CodeAnalysis.ExternalAccess.Xaml.IResolveCachedDataService.ToResolveData(object! data, System.Uri! uri) -> object! +Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler.HandleRequestAsync(TRequest request, Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlRequestHandler.MutatesSolutionState.get -> bool @@ -51,6 +53,10 @@ Microsoft.CodeAnalysis.ExternalAccess.Xaml.ResolveDataConversions Microsoft.CodeAnalysis.ExternalAccess.Xaml.StringConstants Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlCommandAttribute Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlCommandAttribute.XamlCommandAttribute(string! command) -> void +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSource.XamlDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource! xamlDiagnosticSource, Microsoft.CodeAnalysis.TextDocument! document) -> void +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlDiagnosticSourceProvider.XamlDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.Xaml.IXamlDiagnosticSource? xamlDiagnosticSource) -> void Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlMethodAttribute Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlMethodAttribute.XamlMethodAttribute(string! method) -> void Microsoft.CodeAnalysis.ExternalAccess.Xaml.XamlRequestContext From 0b1dd0eb992a9d3d8a68cc6baa06ebaeae7e18fe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 13:14:53 -0700 Subject: [PATCH 205/292] use var --- .../WorkspaceServiceTests/TemporaryStorageServiceTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index e8280a88731fa..536807def8364 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -149,9 +149,9 @@ public void TestTemporaryStorageMemoryMappedFileManagement() await Task.Yield(); - using Stream s1 = service.ReadFromTemporaryStorageService(storage1.Identifier, CancellationToken.None); - using Stream s2 = service.ReadFromTemporaryStorageService(storage2.Identifier, CancellationToken.None); - using Stream s3 = service.ReadFromTemporaryStorageService(storage3.Identifier, CancellationToken.None); + using var s1 = service.ReadFromTemporaryStorageService(storage1.Identifier, CancellationToken.None); + using var s2 = service.ReadFromTemporaryStorageService(storage2.Identifier, CancellationToken.None); + using var s3 = service.ReadFromTemporaryStorageService(storage3.Identifier, CancellationToken.None); Assert.Equal(1024 * i - 1, s1.Length); Assert.Equal(1024 * i, s2.Length); Assert.Equal(1024 * i + 1, s3.Length); From 8a191b3d8ca0e7707a166510d245c0a443713bc2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 13:21:25 -0700 Subject: [PATCH 206/292] Ensure handles are held --- .../VisualStudioMetadataReferenceManager.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 6823a8729ce65..3f6a31a958081 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -246,17 +246,25 @@ static void StreamCopy(Stream source, Stream destination, int start, int length) /// private AssemblyMetadata CreateAssemblyMetadataFromMetadataImporter(FileKey fileKey) { - return CreateAssemblyMetadata(fileKey, fileKey => + using var _ = ArrayBuilder.GetInstance(out var storageHandles); + var newMetadata = CreateAssemblyMetadata(fileKey, fileKey => { var metadata = TryCreateModuleMetadataFromMetadataImporter(fileKey); // getting metadata didn't work out through importer. fallback to shadow copy one if (metadata == null) - GetMetadataFromTemporaryStorage(fileKey, out _, out metadata); + { + GetMetadataFromTemporaryStorage(fileKey, out var storageHandle, out metadata); + storageHandles.Add(storageHandle); + } return metadata; }); + s_metadataToStorageHandles.Add(newMetadata, storageHandles.ToImmutable()); + + return newMetadata; + ModuleMetadata? TryCreateModuleMetadataFromMetadataImporter(FileKey moduleFileKey) { if (!TryGetFileMappingFromMetadataImporter(moduleFileKey, out var info, out var pImage, out var length)) From 950eaed3116d5df8569b50a3b0f9767e42f87b7f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 13:29:59 -0700 Subject: [PATCH 207/292] unify code --- .../VisualStudioMetadataReferenceManager.cs | 57 +++++++------------ 1 file changed, 20 insertions(+), 37 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 3f6a31a958081..c62c8a82e1631 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -154,36 +154,23 @@ AssemblyMetadata GetMetadataWorker() else { // use temporary storage - using var _ = ArrayBuilder.GetInstance(out var storageHandles); - var newMetadata = CreateAssemblyMetadata(key, key => - { - // - // - GetMetadataFromTemporaryStorage(key, out var storageHandle, out var metadata); - storageHandles.Add(storageHandle); - return metadata; - }); - - s_metadataToStorageHandles.Add(newMetadata, storageHandles.ToImmutable()); - - return newMetadata; + return CreateAssemblyMetadata(key, GetMetadataFromTemporaryStorage); } } } - private void GetMetadataFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageHandle storageHandle, out ModuleMetadata metadata) + private (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) GetMetadataFromTemporaryStorage( + FileKey moduleFileKey) { - GetStorageInfoFromTemporaryStorage(moduleFileKey, out storageHandle, out var stream); + GetStorageInfoFromTemporaryStorage(moduleFileKey, out var storageHandle, out var stream); unsafe { // For an unmanaged memory stream, ModuleMetadata can take ownership directly. - metadata = ModuleMetadata.CreateFromMetadata((IntPtr)stream.PositionPointer, (int)stream.Length, stream.Dispose); + var metadata = ModuleMetadata.CreateFromMetadata((IntPtr)stream.PositionPointer, (int)stream.Length, stream.Dispose); + return (metadata, storageHandle); } - return; - void GetStorageInfoFromTemporaryStorage( FileKey moduleFileKey, out TemporaryStorageHandle storageHandle, out UnmanagedMemoryStream stream) { @@ -246,25 +233,16 @@ static void StreamCopy(Stream source, Stream destination, int start, int length) /// private AssemblyMetadata CreateAssemblyMetadataFromMetadataImporter(FileKey fileKey) { - using var _ = ArrayBuilder.GetInstance(out var storageHandles); - var newMetadata = CreateAssemblyMetadata(fileKey, fileKey => + return CreateAssemblyMetadata(fileKey, fileKey => { var metadata = TryCreateModuleMetadataFromMetadataImporter(fileKey); + if (metadata != null) + return (metadata, storageHandle: null); // getting metadata didn't work out through importer. fallback to shadow copy one - if (metadata == null) - { - GetMetadataFromTemporaryStorage(fileKey, out var storageHandle, out metadata); - storageHandles.Add(storageHandle); - } - - return metadata; + return GetMetadataFromTemporaryStorage(fileKey); }); - s_metadataToStorageHandles.Add(newMetadata, storageHandles.ToImmutable()); - - return newMetadata; - ModuleMetadata? TryCreateModuleMetadataFromMetadataImporter(FileKey moduleFileKey) { if (!TryGetFileMappingFromMetadataImporter(moduleFileKey, out var info, out var pImage, out var length)) @@ -319,11 +297,13 @@ bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)] /// private static AssemblyMetadata CreateAssemblyMetadata( FileKey fileKey, - Func moduleMetadataFactory) + Func moduleMetadataFactory) { - var manifestModule = moduleMetadataFactory(fileKey); + using var _1 = ArrayBuilder.GetInstance(out var storageHandles); + var (manifestModule, storageHandle) = moduleMetadataFactory(fileKey); + storageHandles.AddIfNotNull(storageHandle); - using var _ = ArrayBuilder.GetInstance(out var moduleBuilder); + using var _2 = ArrayBuilder.GetInstance(out var moduleBuilder); string? assemblyDir = null; foreach (var moduleName in manifestModule.GetModuleNames()) @@ -336,14 +316,17 @@ private static AssemblyMetadata CreateAssemblyMetadata( // Suppression should be removed or addressed https://github.com/dotnet/roslyn/issues/41636 var moduleFileKey = FileKey.Create(PathUtilities.CombineAbsoluteAndRelativePaths(assemblyDir, moduleName)!); - var metadata = moduleMetadataFactory(moduleFileKey); + var (metadata, metadataStorageHandle) = moduleMetadataFactory(moduleFileKey); moduleBuilder.Add(metadata); + storageHandles.AddIfNotNull(metadataStorageHandle); } if (moduleBuilder.Count == 0) moduleBuilder.Add(manifestModule); - return AssemblyMetadata.Create(moduleBuilder.ToImmutable()); + var result = AssemblyMetadata.Create(moduleBuilder.ToImmutable()); + s_metadataToStorageHandles.Add(result, storageHandles.ToImmutable()); + return result; } } From 350329a08a255637b949af9b9d2c690cebfda233 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 23 Apr 2024 07:18:57 +1000 Subject: [PATCH 208/292] Update src/Tools/ExternalAccess/Razor/Cohost/Constants.cs Co-authored-by: ady109 <149323961+ady109@users.noreply.github.com> --- src/Tools/ExternalAccess/Razor/Cohost/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs index be036e816f43f..71a70b2de65d9 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/Constants.cs @@ -18,6 +18,6 @@ internal static class Constants public static readonly ImmutableArray RazorLanguage = ImmutableArray.Create("Razor"); - // The UI context is provided by Razor, so this going must match the one in https://github.com/dotnet/razor/blob/main/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs + // The UI context is provided by Razor, so this guid must match the one in https://github.com/dotnet/razor/blob/main/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorConstants.cs public static readonly Guid RazorCohostingUIContext = new Guid("6d5b86dc-6b8a-483b-ae30-098a3c7d6774"); } From b5c012a9daadfb4dd9c856be6c767f3fa6e4135a Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 23 Apr 2024 08:25:32 +1000 Subject: [PATCH 209/292] Fix build errors --- eng/Directory.Packages.props | 6 +++--- .../Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index d9970c400f1f0..64508f78d71f5 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -38,9 +38,9 @@ - - - + + + diff --git a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj index 8cbf092883199..96f6c75476969 100644 --- a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj +++ b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj @@ -49,7 +49,7 @@ - + From c2f3410958ea18517595eda80104f0e6d5d8018f Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Mon, 22 Apr 2024 15:31:08 -0700 Subject: [PATCH 210/292] Fixing tests --- ...TypeScriptPullDiagnosticHandlerProvider.cs | 7 ++- ...AbstractWorkspacePullDiagnosticsHandler.cs | 10 +--- ... => AggregatedDocumentDiagnosticSource.cs} | 32 ++++++---- .../DiagnosticSourceManager.cs | 58 +++++++++++-------- .../IDiagnosticSourceManager.cs | 2 +- .../DocumentPullDiagnosticHandler.cs | 9 +-- ...ntaxAndSemanticDiagnosticSourceProvider.cs | 10 ++-- .../PublicDocumentPullDiagnosticsHandler.cs | 3 +- ...cePullDiagnosticsHandler_IOnInitialized.cs | 45 +++++++++----- 9 files changed, 100 insertions(+), 76 deletions(-) rename src/Features/LanguageServer/Protocol/Handler/Diagnostics/{Public/PublicDocumentDiagnosticSourceProvider.cs => AggregatedDocumentDiagnosticSource.cs} (62%) diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs index da2e663f06bfd..b5527e89596a0 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; @@ -17,8 +18,9 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(analyzerService, diagnosticsRefresher, globalOptions) + IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { } @@ -28,7 +30,8 @@ internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( internal class VSTypeScriptWorkspacePullDiagnosticHandler( LspWorkspaceRegistrationService registrationService, IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, analyzerService, diagnosticsRefresher, globalOptions) + IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index c0872bae69c2d..4e97b9b6231e0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -53,14 +53,8 @@ public void Dispose() protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) { - if (GetDiagnosticCategory(diagnosticsParams) is string sourceName) - { - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, false, cancellationToken); - } - else - { - return new([]); - } + var sourceName = GetDiagnosticCategory(diagnosticsParams); + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, false, cancellationToken); } private void OnLspSolutionChanged(object? sender, WorkspaceChangeEventArgs e) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs similarity index 62% rename from src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs rename to src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs index a992885c06e04..d12148e32eb62 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs @@ -4,23 +4,25 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.PooledObjects; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; /// /// Aggregates multiple source diagnostics /// -internal sealed class PublicDocumentDiagnosticSource : AbstractDocumentDiagnosticSource +internal sealed class AggregatedDocumentDiagnosticSource : AbstractDocumentDiagnosticSource { private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly string? _sourceName; - public PublicDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, TextDocument document, string? sourceName) : base(document) + public AggregatedDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, + TextDocument document, string? sourceName) : base(document) { _diagnosticSourceManager = diagnosticSourceManager; _sourceName = sourceName; @@ -31,17 +33,14 @@ public PublicDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceM public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var diagnostics); - var isLive = this.IsLiveSource(); foreach (var name in GetSourceNames()) { var sources = await _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, name, true, cancellationToken).ConfigureAwait(false); foreach (var source in sources) { - if (source.IsLiveSource() == isLive) - { - var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); - diagnostics.AddRange(namedDiagnostics); - } + Debug.Assert(source.IsLiveSource(), "All document sources should be live"); + var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); + diagnostics.AddRange(namedDiagnostics); } } @@ -49,5 +48,18 @@ public override async Task> GetDiagnosticsAsync(R } private IEnumerable GetSourceNames() - => string.IsNullOrEmpty(this._sourceName) ? _diagnosticSourceManager.GetSourceNames(isDocument: true) : [_sourceName]; + { + if (this._sourceName != null) + { + yield return this._sourceName; + } + else + { + foreach (var name in _diagnosticSourceManager.GetSourceNames(isDocument: true)) + { + if (name != PullDiagnosticCategories.Task) + yield return name; + } + } + } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 03321965e977b..448332f73132e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -11,40 +11,48 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Export(typeof(IDiagnosticSourceManager)), Shared] +internal class DiagnosticSourceManager : IDiagnosticSourceManager { - [Export(typeof(IDiagnosticSourceManager)), Shared] - internal class DiagnosticSourceManager : IDiagnosticSourceManager - { - private readonly ImmutableDictionary _documentProviders; - private readonly ImmutableDictionary _workspaceProviders; + private readonly ImmutableDictionary _documentProviders; + private readonly ImmutableDictionary _workspaceProviders; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DiagnosticSourceManager([ImportMany] IEnumerable sourceProviders) - { - _documentProviders = sourceProviders - .Where(p => p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public DiagnosticSourceManager([ImportMany] IEnumerable sourceProviders) + { + _documentProviders = sourceProviders + .Where(p => p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); - _workspaceProviders = sourceProviders - .Where(p => !p.IsDocument) - .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); - } + _workspaceProviders = sourceProviders + .Where(p => !p.IsDocument) + .ToImmutableDictionary(kvp => kvp.Name, kvp => kvp); + } - /// - public IEnumerable GetSourceNames(bool isDocument) - => (isDocument ? _documentProviders : _workspaceProviders).Keys; + /// + public IEnumerable GetSourceNames(bool isDocument) + => (isDocument ? _documentProviders : _workspaceProviders).Keys; - /// - public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) + /// + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken) + { + var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; + if (sourceName != null) { - var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; if (providersDictionary.TryGetValue(sourceName, out var provider)) return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); - - return new([]); } + else if (isDocument) + { + if (context.TextDocument is { } document) + return new([new AggregatedDocumentDiagnosticSource(this, document, null)]); + } + + return new([]); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs index 3246e5cc9153c..b7849dac006e1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs @@ -27,6 +27,6 @@ internal interface IDiagnosticSourceManager /// Source name. /// True for document sources and false for workspace sources. /// The cancellation token. - ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken); + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index 401bbcb314cd3..e037834c4bc80 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -74,14 +74,7 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bo protected override ValueTask> GetOrderedDiagnosticSourcesAsync( VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) { - if (diagnosticsParams.QueryingDiagnosticKind?.Value is string sourceName) - { - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, true, cancellationToken); - } - else - { - return new([]); - } + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, diagnosticsParams.QueryingDiagnosticKind?.Value, true, cancellationToken); } protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs index d937791b2aeff..a33f882167d5e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -39,7 +39,7 @@ public override ValueTask> CreateDiagnosticSou [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, - DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSyntax) + DiagnosticKind.CompilerSyntax, PullDiagnosticCategories.DocumentCompilerSyntax) { } @@ -55,18 +55,18 @@ internal sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] [ExportDiagnosticSourceProvider, Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + internal sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, - DiagnosticKind.AnalyzerSemantic, PullDiagnosticCategories.DocumentAnalyzerSemantic) + DiagnosticKind.AnalyzerSyntax, PullDiagnosticCategories.DocumentAnalyzerSyntax) { } [ExportDiagnosticSourceProvider, Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - internal sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + internal sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, - DiagnosticKind.AnalyzerSyntax, PullDiagnosticCategories.DocumentAnalyzerSyntax) + DiagnosticKind.AnalyzerSemantic, PullDiagnosticCategories.DocumentAnalyzerSemantic) { } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index c64cffaf2496d..19db1af4f9add 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -97,7 +96,7 @@ protected override ValueTask> GetOrderedDiagno { // Wrap all sources into ISourceProvider so that we can keep using DocumentDiagnosticReport // (which supports a single source) - return new([new PublicDocumentDiagnosticSource(_diagnosticSourceManager, document, diagnosticParams.Identifier)]); + return new([new AggregatedDocumentDiagnosticSource(_diagnosticSourceManager, document, diagnosticParams.Identifier)]); } return new([]); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index 7d3260b36184a..ed55a6164df3f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public @@ -13,24 +14,38 @@ internal sealed partial class PublicWorkspacePullDiagnosticsHandler : IOnInitial { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - var sources = _diagnosticSourceManager.GetSourceNames(isDocument: false); - var regParams = new RegistrationParams + if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true && IsFsaEnabled()) { - Registrations = sources.Select(FromSourceName).ToArray() - }; - regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics - await _clientLanguageServerManager.SendRequestAsync( - methodName: Methods.ClientRegisterCapabilityName, - @params: regParams, - cancellationToken).ConfigureAwait(false); + var sources = _diagnosticSourceManager.GetSourceNames(isDocument: false); + var regParams = new RegistrationParams + { + Registrations = sources.Select(FromSourceName).ToArray() + }; + regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics + await _clientLanguageServerManager.SendRequestAsync( + methodName: Methods.ClientRegisterCapabilityName, + @params: regParams, + cancellationToken).ConfigureAwait(false); - Registration FromSourceName(string sourceName) - => new() + Registration FromSourceName(string sourceName) + => new() + { + Id = sourceName, + Method = Methods.WorkspaceDiagnosticName, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } + }; + } + + bool IsFsaEnabled() { - Id = sourceName, - Method = Methods.WorkspaceDiagnosticName, - RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } - }; + foreach (var language in context.SupportedLanguages) + { + if (GlobalOptions.GetBackgroundAnalysisScope(language) == BackgroundAnalysisScope.FullSolution) + return true; + } + + return false; + } } } } From 78fac7d962617ce140f0856364050e2c676c0289 Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Mon, 22 Apr 2024 15:48:38 -0700 Subject: [PATCH 211/292] Remove reflection code for C# code mapper --- .../CodeMapper/CopilotCSharpMapCodeService.cs | 52 +------------------ 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs b/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs index aa8265e7d4dbb..7a20c142784b2 100644 --- a/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs +++ b/src/Tools/ExternalAccess/Copilot/Internal/CodeMapper/CopilotCSharpMapCodeService.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper; @@ -16,8 +15,6 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.CodeMapper; -using MapCodeAsyncDelegateType = Func, ImmutableArray<(Document Document, TextSpan TextSpan)>, Dictionary, CancellationToken, Task?>>; - [ExportLanguageService(typeof(IMapCodeService), language: LanguageNames.CSharp), Shared] internal sealed class CSharpMapCodeService : IMapCodeService { @@ -25,9 +22,9 @@ internal sealed class CSharpMapCodeService : IMapCodeService [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpMapCodeService([Import(AllowDefault = true)] ICSharpCopilotMapCodeService? service) + public CSharpMapCodeService(ICSharpCopilotMapCodeService service) { - _service = service ?? new ReflectionWrapper(); + _service = service; } public Task?> MapCodeAsync(Document document, ImmutableArray contents, ImmutableArray<(Document, TextSpan)> focusLocations, CancellationToken cancellationToken) @@ -35,49 +32,4 @@ public CSharpMapCodeService([Import(AllowDefault = true)] ICSharpCopilotMapCodeS var options = new Dictionary(); return _service.MapCodeAsync(document, contents, focusLocations, options, cancellationToken); } - - private sealed class ReflectionWrapper : ICSharpCopilotMapCodeService - { - private const string CodeMapperDllName = "Microsoft.VisualStudio.Copilot.CodeMappers.CSharp, Version=0.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; - private const string MapCodeServiceTypeFullName = "Microsoft.VisualStudio.Conversations.CodeMappers.CSharp.CSharpMapCodeService"; - private const string MapCodeAsyncMethodName = "MapCodeAsync"; - - // Create and cache the delegate to ensure we use a singleton and better performance. - private readonly Lazy _lazyMapCodeAsyncDelegate = new(CreateDelegate, LazyThreadSafetyMode.PublicationOnly); - - private static MapCodeAsyncDelegateType? CreateDelegate() - { - try - { - var assembly = Assembly.Load(CodeMapperDllName); - var type = assembly.GetType(MapCodeServiceTypeFullName); - if (type is null) - return null; - - var serviceInstance = Activator.CreateInstance(type); - if (serviceInstance is null) - return null; - - if (type.GetMethod(MapCodeAsyncMethodName, [typeof(Document), typeof(ImmutableArray), typeof(ImmutableArray<(Document Document, TextSpan TextSpan)>), typeof(Dictionary), typeof(CancellationToken)]) is not MethodInfo mapCodeAsyncMethod) - return null; - - return (MapCodeAsyncDelegateType)Delegate.CreateDelegate(typeof(MapCodeAsyncDelegateType), serviceInstance, mapCodeAsyncMethod); - - } - catch - { - // Catch all here since failure is expected if user has no copilot chat or an older version of it installed. - } - - return null; - } - - public async Task?> MapCodeAsync(Document document, ImmutableArray contents, ImmutableArray<(Document document, TextSpan textSpan)> prioritizedFocusLocations, Dictionary options, CancellationToken cancellationToken) - { - if (_lazyMapCodeAsyncDelegate.Value is null) - return null; - - return await _lazyMapCodeAsyncDelegate.Value(document, contents, prioritizedFocusLocations, options, cancellationToken).ConfigureAwait(false); - } - } } From f8df12304b6dcf9e51b4a353ae6fe396d66011ca Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Mon, 22 Apr 2024 15:50:03 -0700 Subject: [PATCH 212/292] Fix example in cookbook (#73184) --- docs/features/incremental-generators.cookbook.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/incremental-generators.cookbook.md b/docs/features/incremental-generators.cookbook.md index 9bbb0b9134c9d..3cf0870f6bf57 100644 --- a/docs/features/incremental-generators.cookbook.md +++ b/docs/features/incremental-generators.cookbook.md @@ -191,7 +191,7 @@ public class FileTransformGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { var pipeline = context.AdditionalTextsProvider - .Where(static (text, cancellationToken) => text.Path.EndsWith(".xml")) + .Where(static (text) => text.Path.EndsWith(".xml")) .Select(static (text, cancellationToken) => { var name = Path.GetFileName(text.Path); From 43ae3a426164384b74ee137117c84da2ded04400 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 16:17:21 -0700 Subject: [PATCH 213/292] fixup --- .../VisualStudioMetadataReferenceManager.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index c62c8a82e1631..ab17d0e8033e2 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -166,7 +166,9 @@ AssemblyMetadata GetMetadataWorker() unsafe { - // For an unmanaged memory stream, ModuleMetadata can take ownership directly. + // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Passing in stream.Dispose + // here will also ensure that as long as this metdata is alive, we'll keep the memory-mapped-file it points + // to alive. var metadata = ModuleMetadata.CreateFromMetadata((IntPtr)stream.PositionPointer, (int)stream.Length, stream.Dispose); return (metadata, storageHandle); } @@ -299,9 +301,9 @@ private static AssemblyMetadata CreateAssemblyMetadata( FileKey fileKey, Func moduleMetadataFactory) { - using var _1 = ArrayBuilder.GetInstance(out var storageHandles); + using var _1 = ArrayBuilder.GetInstance(out var storageHandles); var (manifestModule, storageHandle) = moduleMetadataFactory(fileKey); - storageHandles.AddIfNotNull(storageHandle); + storageHandles.Add(storageHandle); using var _2 = ArrayBuilder.GetInstance(out var moduleBuilder); @@ -319,14 +321,20 @@ private static AssemblyMetadata CreateAssemblyMetadata( var (metadata, metadataStorageHandle) = moduleMetadataFactory(moduleFileKey); moduleBuilder.Add(metadata); - storageHandles.AddIfNotNull(metadataStorageHandle); + storageHandles.Add(metadataStorageHandle); } if (moduleBuilder.Count == 0) moduleBuilder.Add(manifestModule); var result = AssemblyMetadata.Create(moduleBuilder.ToImmutable()); - s_metadataToStorageHandles.Add(result, storageHandles.ToImmutable()); + + // If we got any null handles, then we weren't able to map this whole assembly into memory mapped files. So we + // can't use those to transfer over the data efficiently to the OOP process. In that case, we don't store the + // handles at all. + if (storageHandles.Count > 0 && storageHandles.All(h => h != null)) + s_metadataToStorageHandles.Add(result, storageHandles.ToImmutable()); + return result; } } From a4cfcd3f7770e813897f6f1b87027adfb2a2092a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 16:59:06 -0700 Subject: [PATCH 214/292] Simplify --- .../Host/TemporaryStorage/TemporaryStorageHandle.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs index fe626adafa692..b4a7343dfdbc1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs @@ -17,13 +17,7 @@ namespace Microsoft.CodeAnalysis.Host; /// internal sealed class TemporaryStorageHandle(MemoryMappedFile? memoryMappedFile, TemporaryStorageIdentifier identifier) { -#pragma warning disable IDE0052 // Remove unread private members - /// - /// This field is intentionally not read. It exists just to root the memory mapped file and keep it alive as long - /// as this handle is alive. - /// - private readonly MemoryMappedFile? _memoryMappedFile = memoryMappedFile; -#pragma warning restore IDE0052 // Remove unread private members + public readonly MemoryMappedFile? MemoryMappedFile = memoryMappedFile; private readonly TemporaryStorageIdentifier? _identifier = identifier; public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); From fb243c241804058d6d6b273b2afae750e31fe149 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 17:03:00 -0700 Subject: [PATCH 215/292] remove gc.keepalive --- ...lutionCompilationState.SkeletonReferenceCache.cs | 13 +++++-------- ...SolutionCompilationState.SkeletonReferenceSet.cs | 4 ++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index d030944c9521e..6a7b2b9eca75c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -218,7 +218,7 @@ public readonly SkeletonReferenceCache Clone() private static SkeletonReferenceSet? CreateSkeletonSet( SolutionServices services, Compilation compilation, CancellationToken cancellationToken) { - var metadata = TryCreateMetadata(); + var (metadata, storageHandle) = TryCreateMetadataAndHandle(); if (metadata == null) return null; @@ -226,10 +226,11 @@ public readonly SkeletonReferenceCache Clone() // the stream as well. return new SkeletonReferenceSet( metadata, + storageHandle, compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - AssemblyMetadata? TryCreateMetadata() + (AssemblyMetadata? metadata, TemporaryStorageHandle storageHandle) TryCreateMetadataAndHandle() { cancellationToken.ThrowIfCancellationRequested(); @@ -261,11 +262,7 @@ public readonly SkeletonReferenceCache Clone() var result = AssemblyMetadata.CreateFromStream( temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); - // Note: because we are using a memory-mapped file, we need to keep the handle alive during - // the call to ReadFromTemporaryStorageService. Otherwise, the memory-mapped file could be - // released before we read what we need out of it. - GC.KeepAlive(handle); - return result; + return (result, handle); } if (logger != null) @@ -287,7 +284,7 @@ public readonly SkeletonReferenceCache Clone() m["Errors"] = string.Join(";", groups); })); - return null; + return (null, null!); } } finally diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs index 033c900fecff5..4d4020f6ec71c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using Microsoft.CodeAnalysis.Host; namespace Microsoft.CodeAnalysis; @@ -18,6 +19,7 @@ internal partial class SolutionCompilationState /// private sealed class SkeletonReferenceSet( AssemblyMetadata metadata, + TemporaryStorageHandle storageHandle, string? assemblyName, DeferredDocumentationProvider documentationProvider) { @@ -29,6 +31,8 @@ private sealed class SkeletonReferenceSet( /// private readonly Dictionary _referenceMap = []; + public TemporaryStorageHandle StorageHandle => storageHandle; + public PortableExecutableReference GetOrCreateMetadataReference(MetadataReferenceProperties properties) { lock (_referenceMap) From 27b96ea1b39bc6705acf7ad218f6aab099f51295 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 17:05:20 -0700 Subject: [PATCH 216/292] naming --- .../TemporaryStorageServiceTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 536807def8364..83d5808b72ad2 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -143,15 +143,15 @@ public void TestTemporaryStorageMemoryMappedFileManagement() { for (var j = 1; j < 5; j++) { - var storage1 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1), CancellationToken.None); - var storage2 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i), CancellationToken.None); - var storage3 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); + var handle1 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i - 1), CancellationToken.None); + var handle2 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i), CancellationToken.None); + var handle3 = service.WriteToTemporaryStorage(new MemoryStream(buffer.GetBuffer(), 0, 1024 * i + 1), CancellationToken.None); await Task.Yield(); - using var s1 = service.ReadFromTemporaryStorageService(storage1.Identifier, CancellationToken.None); - using var s2 = service.ReadFromTemporaryStorageService(storage2.Identifier, CancellationToken.None); - using var s3 = service.ReadFromTemporaryStorageService(storage3.Identifier, CancellationToken.None); + using var s1 = service.ReadFromTemporaryStorageService(handle1.Identifier, CancellationToken.None); + using var s2 = service.ReadFromTemporaryStorageService(handle2.Identifier, CancellationToken.None); + using var s3 = service.ReadFromTemporaryStorageService(handle3.Identifier, CancellationToken.None); Assert.Equal(1024 * i - 1, s1.Length); Assert.Equal(1024 * i, s2.Length); Assert.Equal(1024 * i + 1, s3.Length); From 40f0c742134d6d31052c7953f5dc2f0577dfbcf2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 17:43:30 -0700 Subject: [PATCH 217/292] Simplify handle api --- .../VisualStudioMetadataReference.Snapshot.cs | 2 +- .../VisualStudioMetadataReferenceManager.cs | 4 +- .../Serialization/ISupportTemporaryStorage.cs | 2 +- .../SerializerService_Reference.cs | 8 ++-- .../TemporaryStorageService.cs | 39 ++++++++++++------- .../ITemporaryStorageService.cs | 19 +++------ .../TemporaryStorageHandle.cs | 16 +++++--- .../TrivialTemporaryStorageService.cs | 18 ++++----- .../ProjectSystemProjectOptionsProcessor.cs | 9 ++--- ...CompilationState.SkeletonReferenceCache.cs | 4 +- ...onCompilationState.SkeletonReferenceSet.cs | 4 +- .../TemporaryStorageServiceTests.cs | 20 +++++----- 12 files changed, 78 insertions(+), 67 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 8c407e68825d5..81388674db446 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList StorageHandles + public IReadOnlyList StorageHandles => _provider.GetStorageHandles(this.FilePath, _timestamp.Value); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index ab17d0e8033e2..cee1d9ecc31d6 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -22,6 +22,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; +using static TemporaryStorageService; + /// /// Manages metadata references for VS projects. /// @@ -208,7 +210,7 @@ void GetStorageInfoFromTemporaryStorage( } // Now, read the data from the memory-mapped-file back into a stream that we load into the metadata value. - stream = _temporaryStorageService.ReadFromTemporaryStorageService(storageHandle.Identifier, CancellationToken.None); + stream = storageHandle.ReadFromTemporaryStorage(CancellationToken.None); // stream size must be same as what metadata reader said the size should be. Contract.ThrowIfFalse(stream.Length == size); } diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index ffb6efafc2523..84dc4dc0834bd 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -14,5 +14,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? StorageHandles { get; } + IReadOnlyList? StorageHandles { get; } } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 49b572c70c606..d81de69768748 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -16,6 +16,8 @@ namespace Microsoft.CodeAnalysis.Serialization; +using static TemporaryStorageService; + internal partial class SerializerService { private const int MetadataFailed = int.MaxValue; @@ -307,7 +309,7 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( PortableExecutableReference reference, - IReadOnlyList handles, + IReadOnlyList handles, ObjectWriter writer, CancellationToken cancellationToken) { @@ -405,7 +407,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they // sent us the full contents. - var unmanagedStream = _storageService.ReadFromTemporaryStorageService(storageHandle.Identifier, cancellationToken); + var unmanagedStream = storageHandle.ReadFromTemporaryStorage(cancellationToken); Contract.ThrowIfFalse(storageHandle.Identifier.Size == unmanagedStream.Length); // For an unmanaged memory stream, ModuleMetadata can take ownership directly. Stream will be kept alive as @@ -504,7 +506,7 @@ private sealed class SerializedMetadataReference : PortableExecutableReference, private readonly ImmutableArray _storageHandles; private readonly DocumentationProvider _provider; - public IReadOnlyList StorageHandles => _storageHandles; + public IReadOnlyList StorageHandles => _storageHandles; public SerializedMetadataReference( MetadataReferenceProperties properties, diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index cd998a0d2403b..87c1bc80d50c9 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -101,28 +101,22 @@ public TemporaryTextStorage AttachTemporaryTextStorage( string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); + ITemporaryStorageHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + => WriteToTemporaryStorage(stream, cancellationToken); + public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; var storage = new TemporaryStreamStorage(this); storage.WriteStream(stream, cancellationToken); var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); - return new(storage.MemoryMappedInfo.MemoryMappedFile, identifier); - } - - Stream ITemporaryStorageServiceInternal.ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) - => ReadFromTemporaryStorageService(storageIdentifier, cancellationToken); - - public UnmanagedMemoryStream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) - { - var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); - return storage.ReadStream(cancellationToken); + return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); } internal TemporaryStorageHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) { - var storage = new TemporaryStreamStorage(this, storageIdentifier.Name, storageIdentifier.Offset, storageIdentifier.Size); - return new(storage.MemoryMappedInfo.MemoryMappedFile, storageIdentifier); + var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); + return new(this, memoryMappedFile, storageIdentifier); } /// @@ -170,6 +164,22 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) public static string CreateUniqueName(long size) => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); + public sealed class TemporaryStorageHandle( + TemporaryStorageService storageService, MemoryMappedFile memoryMappedFile, TemporaryStorageIdentifier identifier) : ITemporaryStorageHandle + { + public TemporaryStorageIdentifier Identifier => identifier; + + Stream ITemporaryStorageHandle.ReadFromTemporaryStorage(CancellationToken cancellationToken) + => ReadFromTemporaryStorage(cancellationToken); + + public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage( + storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); + return storage.ReadStream(cancellationToken); + } + } + public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName { private readonly TemporaryStorageService _service; @@ -316,10 +326,11 @@ internal sealed class TemporaryStreamStorage public TemporaryStreamStorage(TemporaryStorageService service) => _service = service; - public TemporaryStreamStorage(TemporaryStorageService service, string storageName, long offset, long size) + public TemporaryStreamStorage( + TemporaryStorageService service, MemoryMappedFile file, string storageName, long offset, long size) { _service = service; - _memoryMappedInfo = MemoryMappedInfo.OpenExisting(storageName, offset, size); + _memoryMappedInfo = new MemoryMappedInfo(file, storageName, offset, size); } public MemoryMappedInfo MemoryMappedInfo => _memoryMappedInfo ?? throw new InvalidOperationException(); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 5636c4b941e0b..9b86919667133 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -22,17 +22,16 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService { /// /// Write the provided to a new memory-mapped-file. Returns a handle to the data that can - /// be used to identify the data across processes allowing it to be read back in in any process. Note: the data - /// will not longer be readable if the returned is disposed. + /// be used to identify the data across processes allowing it to be read back in in any process. /// /// /// This type is used for two purposes. /// /// - /// Dumping metadata to disk. This then allowing them to be read in by mapping - /// their data into types like . It also allows them to be read in by our server - /// process, without having to transmit the data over the wire. For this use case, we never dispose of the handle, - /// opting to keep things simple by having the host and server not have to coordinate on the lifetime of the data. + /// Dumping metadata to disk. This then allowing them to be read in by mapping their data into types like . It also allows them to be read in by our server process, without having to transmit + /// the data over the wire. For this use case, we never dispose of the handle, opting to keep things simple by + /// having the host and server not have to coordinate on the lifetime of the data. /// /// /// Dumping large compiler command lines to disk to purge them from main memory. Some of these strings are enormous @@ -45,13 +44,7 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// cref="Stream.Position"/> 0 within this method. The caller does not need to reset the stream /// itself. /// - TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); - - /// - /// Reads the data indicated to by the into a stream. This stream can be - /// created in a different process than the one that wrote the data originally. - /// - Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken); + ITemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); ITemporaryTextStorageInternal CreateTemporaryTextStorage(); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs index b4a7343dfdbc1..a1e98f500b35f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.IO; using System.IO.MemoryMappedFiles; using System.Threading; @@ -12,13 +13,16 @@ namespace Microsoft.CodeAnalysis.Host; /// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is /// alive, the data should remain in storage and can be readable from any process using the information provided in . Use to write the data -/// to temporary storage and get a handle to it. Use to read the data back in any process. +/// to temporary storage and get a handle to it. Use to read the data back in +/// any process. /// -internal sealed class TemporaryStorageHandle(MemoryMappedFile? memoryMappedFile, TemporaryStorageIdentifier identifier) +internal interface ITemporaryStorageHandle { - public readonly MemoryMappedFile? MemoryMappedFile = memoryMappedFile; - private readonly TemporaryStorageIdentifier? _identifier = identifier; + public TemporaryStorageIdentifier Identifier { get; } - public TemporaryStorageIdentifier Identifier => _identifier ?? throw new InvalidOperationException("Handle has already been disposed"); + /// + /// Reads the data indicated to by this handle into a stream. This stream can be created in a different process + /// than the one that wrote the data originally. + /// + Stream ReadFromTemporaryStorage(CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index f854e7fd578a1..28f2b4ba4c66e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -17,8 +17,6 @@ internal sealed class TrivialTemporaryStorageService : ITemporaryStorageServiceI { public static readonly TrivialTemporaryStorageService Instance = new(); - private static readonly ConditionalWeakTable s_streamStorage = new(); - private TrivialTemporaryStorageService() { } @@ -26,24 +24,24 @@ private TrivialTemporaryStorageService() public ITemporaryTextStorageInternal CreateTemporaryTextStorage() => new TextStorage(); - public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + public ITemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; var storage = new StreamStorage(); storage.WriteStream(stream); var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: stream.Length); - var handle = new TemporaryStorageHandle(memoryMappedFile: null, identifier); - s_streamStorage.Add(identifier, storage); + var handle = new TrivialStorageHandle(identifier, storage); return handle; } - public Stream ReadFromTemporaryStorageService(TemporaryStorageIdentifier storageIdentifier, CancellationToken cancellationToken) + private sealed class TrivialStorageHandle( + TemporaryStorageIdentifier storageIdentifier, + StreamStorage streamStorage) : ITemporaryStorageHandle { - Contract.ThrowIfFalse( - s_streamStorage.TryGetValue(storageIdentifier, out var streamStorage), - "StorageIdentifier was not created by this storage service!"); + public TemporaryStorageIdentifier Identifier => storageIdentifier; - return streamStorage.ReadStream(); + public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) + => streamStorage.ReadStream(); } private sealed class StreamStorage diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 16878f992c9dd..bca6e7a662d32 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -39,7 +39,7 @@ internal class ProjectSystemProjectOptionsProcessor : IDisposable /// (especially in cases with many references). /// /// Note: this will be null in the case that the command line is an empty array. - private TemporaryStorageHandle? _commandLineStorageHandle; + private ITemporaryStorageHandle? _commandLineStorageHandle; private CommandLineArguments _commandLineArgumentsForCommandLine; private string? _explicitRuleSetFilePath; @@ -247,7 +247,7 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) // includes in the IDE so we can be watching for changes again. var commandLine = _commandLineStorageHandle == null ? ImmutableArray.Empty - : EnumerateLines(_temporaryStorageService, _commandLineStorageHandle.Identifier).ToImmutableArray(); + : EnumerateLines(_commandLineStorageHandle).ToImmutableArray(); DisposeOfRuleSetFile_NoLock(); ReparseCommandLine_NoLock(commandLine); @@ -255,10 +255,9 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) } static IEnumerable EnumerateLines( - ITemporaryStorageServiceInternal temporaryStorageService, - TemporaryStorageIdentifier storageIdentifier) + ITemporaryStorageHandle storageHandle) { - using var stream = temporaryStorageService.ReadFromTemporaryStorageService(storageIdentifier, CancellationToken.None); + using var stream = storageHandle.ReadFromTemporaryStorage(CancellationToken.None); using var reader = new StreamReader(stream); while (reader.ReadLine() is string line) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 6a7b2b9eca75c..0735a5f50c9cf 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -230,7 +230,7 @@ public readonly SkeletonReferenceCache Clone() compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - (AssemblyMetadata? metadata, TemporaryStorageHandle storageHandle) TryCreateMetadataAndHandle() + (AssemblyMetadata? metadata, ITemporaryStorageHandle storageHandle) TryCreateMetadataAndHandle() { cancellationToken.ThrowIfCancellationRequested(); @@ -260,7 +260,7 @@ public readonly SkeletonReferenceCache Clone() // Now read the data back from the stream from the memory mapped file. This will come back as an // UnmanagedMemoryStream, which our assembly/metadata subsystem is optimized around. var result = AssemblyMetadata.CreateFromStream( - temporaryStorageService.ReadFromTemporaryStorageService(handle.Identifier, cancellationToken), leaveOpen: false); + handle.ReadFromTemporaryStorage(cancellationToken), leaveOpen: false); return (result, handle); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs index 4d4020f6ec71c..9d579a586ddf3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs @@ -19,7 +19,7 @@ internal partial class SolutionCompilationState /// private sealed class SkeletonReferenceSet( AssemblyMetadata metadata, - TemporaryStorageHandle storageHandle, + ITemporaryStorageHandle storageHandle, string? assemblyName, DeferredDocumentationProvider documentationProvider) { @@ -31,7 +31,7 @@ private sealed class SkeletonReferenceSet( /// private readonly Dictionary _referenceMap = []; - public TemporaryStorageHandle StorageHandle => storageHandle; + public ITemporaryStorageHandle StorageHandle => storageHandle; public PortableExecutableReference GetOrCreateMetadataReference(MetadataReferenceProperties properties) { diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 83d5808b72ad2..b859c52a4c186 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -18,6 +18,8 @@ namespace Microsoft.CodeAnalysis.UnitTests { + using static TemporaryStorageService; + [UseExportProvider] #if NETCOREAPP [SupportedOSPlatform("windows")] @@ -60,7 +62,7 @@ public void TestTemporaryStorageStream() var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); - using var result = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); + using var result = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(data.Length, result.Length); for (var i = 0; i < SharedPools.ByteBufferSize; i++) @@ -120,7 +122,7 @@ public void TestZeroLengthStreams() handle = service.WriteToTemporaryStorage(stream1, CancellationToken.None); } - using (var stream2 = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None)) + using (var stream2 = handle.ReadFromTemporaryStorage(CancellationToken.None)) { Assert.Equal(0, stream2.Length); } @@ -149,9 +151,9 @@ public void TestTemporaryStorageMemoryMappedFileManagement() await Task.Yield(); - using var s1 = service.ReadFromTemporaryStorageService(handle1.Identifier, CancellationToken.None); - using var s2 = service.ReadFromTemporaryStorageService(handle2.Identifier, CancellationToken.None); - using var s3 = service.ReadFromTemporaryStorageService(handle3.Identifier, CancellationToken.None); + using var s1 = handle1.ReadFromTemporaryStorage(CancellationToken.None); + using var s2 = handle2.ReadFromTemporaryStorage(CancellationToken.None); + using var s3 = handle3.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(1024 * i - 1, s1.Length); Assert.Equal(1024 * i, s2.Length); Assert.Equal(1024 * i + 1, s3.Length); @@ -192,7 +194,7 @@ public void TestTemporaryStorageScaling() for (var i = 0; i < 1024 * 5; i++) { - using var s = service.ReadFromTemporaryStorageService(storageHandles[i].Identifier, CancellationToken.None); + using var s = storageHandles[i].ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(1, s.ReadByte()); } } @@ -214,7 +216,7 @@ public void StreamTest1() var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); + using var stream = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(expected.Length, stream.Length); for (var i = 0; i < expected.Length; i++) @@ -239,7 +241,7 @@ public void StreamTest2() var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); + using var stream = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(expected.Length, stream.Length); var index = 0; @@ -279,7 +281,7 @@ public void StreamTest3() var handle = service.WriteToTemporaryStorage(expected, CancellationToken.None); expected.Position = 0; - using var stream = service.ReadFromTemporaryStorageService(handle.Identifier, CancellationToken.None); + using var stream = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.Equal(expected.Length, stream.Length); for (var i = 0; i < expected.Length; i++) From 0c4240ce2a3fa5d8e7efdeabe5c8033ecad6c668 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 17:47:37 -0700 Subject: [PATCH 218/292] feedback --- .../VisualStudioMetadataReferenceManager.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index cee1d9ecc31d6..6fa856f0c688f 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -303,11 +303,10 @@ private static AssemblyMetadata CreateAssemblyMetadata( FileKey fileKey, Func moduleMetadataFactory) { - using var _1 = ArrayBuilder.GetInstance(out var storageHandles); - var (manifestModule, storageHandle) = moduleMetadataFactory(fileKey); - storageHandles.Add(storageHandle); + var (manifestModule, manifestHandle) = moduleMetadataFactory(fileKey); - using var _2 = ArrayBuilder.GetInstance(out var moduleBuilder); + using var _1 = ArrayBuilder.GetInstance(out var moduleBuilder); + using var _2 = ArrayBuilder.GetInstance(out var storageHandles); string? assemblyDir = null; foreach (var moduleName in manifestModule.GetModuleNames()) @@ -327,14 +326,18 @@ private static AssemblyMetadata CreateAssemblyMetadata( } if (moduleBuilder.Count == 0) + { moduleBuilder.Add(manifestModule); + storageHandles.Add(manifestHandle); + } var result = AssemblyMetadata.Create(moduleBuilder.ToImmutable()); // If we got any null handles, then we weren't able to map this whole assembly into memory mapped files. So we // can't use those to transfer over the data efficiently to the OOP process. In that case, we don't store the // handles at all. - if (storageHandles.Count > 0 && storageHandles.All(h => h != null)) + Contract.ThrowIfTrue(storageHandles.Count == 0); + if (storageHandles.All(h => h != null)) s_metadataToStorageHandles.Add(result, storageHandles.ToImmutable()); return result; From c64f25882f2e2aaec14186178f1fd17d95d21b62 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 17:56:19 -0700 Subject: [PATCH 219/292] rename file --- .../{TemporaryStorageHandle.cs => ITemporaryStorageHandle.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/{TemporaryStorageHandle.cs => ITemporaryStorageHandle.cs} (100%) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageHandle.cs rename to src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs From eedc456250a4681b245e96237d56cbf7fc680a73 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 18:01:15 -0700 Subject: [PATCH 220/292] cleanup --- .../Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs index a1e98f500b35f..3d2be4b29e531 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.IO; -using System.IO.MemoryMappedFiles; using System.Threading; namespace Microsoft.CodeAnalysis.Host; From 7f4f655f063bbba29fe539ea2b05184de0c7c490 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 22 Apr 2024 18:13:35 -0700 Subject: [PATCH 221/292] usnigs --- .../ProjectSystem/ProjectSystemProjectOptionsProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index bca6e7a662d32..950cbab4a7b8a 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; From bf363528f1b974da2480f31125796caff3d92250 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 23 Apr 2024 14:07:26 +1000 Subject: [PATCH 222/292] Revert "Fix build errors" This reverts commit b5c012a9daadfb4dd9c856be6c767f3fa6e4135a. --- eng/Directory.Packages.props | 6 +++--- .../Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 64508f78d71f5..d9970c400f1f0 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -38,9 +38,9 @@ - - - + + + diff --git a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj index 96f6c75476969..bd93fb921807e 100644 --- a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj +++ b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj @@ -49,7 +49,6 @@ - From 26e7c29512a4958fd027320e4866377f8af0f1dc Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 23 Apr 2024 14:44:37 +1000 Subject: [PATCH 223/292] Do UIContext stuff in a service so we don't need to reference VS bits --- .../Utilities/IUIContextActivationService.cs | 15 +++++ .../RazorDynamicRegistrationServiceFactory.cs | 61 ++++++++++++------- .../VisualStudioUIContextActivationService.cs | 23 +++++++ 3 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs create mode 100644 src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs diff --git a/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs b/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs new file mode 100644 index 0000000000000..a68e544abd1b4 --- /dev/null +++ b/src/EditorFeatures/Core/Shared/Utilities/IUIContextActivationService.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities; + +internal interface IUIContextActivationService +{ + /// + /// Executes the specified action when the UIContext first becomes active, or immediately if it is already active + /// + void ExecuteWhenActivated(Guid uiContext, Action action); +} diff --git a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs index 18ca571fa1314..425cfce0e740c 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/RazorDynamicRegistrationServiceFactory.cs @@ -6,10 +6,10 @@ using System.Composition; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.VisualStudio.Shell; using Newtonsoft.Json; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; @@ -19,56 +19,71 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; [ExportCSharpVisualBasicLspServiceFactory(typeof(RazorDynamicRegistrationService), WellKnownLspServerKinds.AlwaysActiveVSLspServer), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class RazorDynamicRegistrationServiceFactory([Import(AllowDefault = true)] Lazy? dynamicRegistrationService) : ILspServiceFactory +internal sealed class RazorDynamicRegistrationServiceFactory( + [Import(AllowDefault = true)] IUIContextActivationService? uIContextActivationService, + [Import(AllowDefault = true)] Lazy? dynamicRegistrationService) : ILspServiceFactory { public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var clientLanguageServerManager = lspServices.GetRequiredService(); - return new RazorDynamicRegistrationService(dynamicRegistrationService, clientLanguageServerManager); + return new RazorDynamicRegistrationService(uIContextActivationService, dynamicRegistrationService, clientLanguageServerManager); } - private class RazorDynamicRegistrationService : ILspService, IOnInitialized + private class RazorDynamicRegistrationService( + IUIContextActivationService? uIContextActivationService, + Lazy? dynamicRegistrationService, + IClientLanguageServerManager? clientLanguageServerManager) : ILspService, IOnInitialized, IDisposable { - private readonly Lazy? _dynamicRegistrationService; - private readonly IClientLanguageServerManager? _clientLanguageServerManager; - private readonly JsonSerializerSettings _serializerSettings; + private readonly CancellationTokenSource _disposalTokenSource = new(); - public RazorDynamicRegistrationService(Lazy? dynamicRegistrationService, IClientLanguageServerManager? clientLanguageServerManager) + public void Dispose() { - _dynamicRegistrationService = dynamicRegistrationService; - _clientLanguageServerManager = clientLanguageServerManager; - - var serializer = new JsonSerializer(); - serializer.AddVSInternalExtensionConverters(); - _serializerSettings = new JsonSerializerSettings { Converters = serializer.Converters }; + _disposalTokenSource.Cancel(); } public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - if (_dynamicRegistrationService is null || _clientLanguageServerManager is null) + if (dynamicRegistrationService is null || clientLanguageServerManager is null) { return Task.CompletedTask; } - var uiContext = UIContext.FromUIContextGuid(Constants.RazorCohostingUIContext); - uiContext.WhenActivated(() => + if (uIContextActivationService is null) { - // Not using the cancellation token passed in, as the context could be activated well after LSP server initialization - InitializeRazor(clientCapabilities, context, CancellationToken.None); - }); + // Outside of VS, we want to initialize immediately.. I think? + InitializeRazor(); + } + else + { + uIContextActivationService.ExecuteWhenActivated(Constants.RazorCohostingUIContext, InitializeRazor); + } return Task.CompletedTask; + + void InitializeRazor() + { + this.InitializeRazor(clientCapabilities, context, _disposalTokenSource.Token); + } } private void InitializeRazor(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { + // The LSP server will dispose us when the server exits, but VS could decide to activate us later. + // If a new instance of the server is created, a new instance of this class will be created and the + // UIContext will already be active, so this method will be immediately called on the new instance. + if (cancellationToken.IsCancellationRequested) return; + + var serializer = new JsonSerializer(); + serializer.AddVSInternalExtensionConverters(); + var serializerSettings = new JsonSerializerSettings { Converters = serializer.Converters }; + // We use a string to pass capabilities to/from Razor to avoid version issues with the Protocol DLL - var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities, _serializerSettings); - var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(_clientLanguageServerManager!); + var serializedClientCapabilities = JsonConvert.SerializeObject(clientCapabilities, serializerSettings); + var razorCohostClientLanguageServerManager = new RazorCohostClientLanguageServerManager(clientLanguageServerManager!); var requestContext = new RazorCohostRequestContext(context); - _dynamicRegistrationService!.Value.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken).ReportNonFatalErrorAsync(); + dynamicRegistrationService!.Value.RegisterAsync(serializedClientCapabilities, requestContext, cancellationToken).ReportNonFatalErrorAsync(); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs b/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs new file mode 100644 index 0000000000000..d04d43a5cd83c --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/VisualStudioUIContextActivationService.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation; + +[Export(typeof(IUIContextActivationService)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class VisualStudioUIContextActivationService() : IUIContextActivationService +{ + public void ExecuteWhenActivated(Guid uiContext, Action action) + { + var context = UIContext.FromUIContextGuid(uiContext); + context.WhenActivated(action); + } +} From a9b690f08634c10b1148102b1eaf49d44d505cfc Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Tue, 23 Apr 2024 10:38:45 +0200 Subject: [PATCH 224/292] Visit binary operators in ref safety analysis (#73081) * Add tests * Visit binary operators in ref safety analysis * Simplify visit tracking --- .../Portable/Binder/RefSafetyAnalysis.cs | 32 ++++++++------ .../Portable/BoundTree/BoundTreeWalker.cs | 13 ++++++ .../Semantic/Semantics/RefEscapingTests.cs | 42 +++++++++++++++++++ 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs index 2029d8d5aa669..04ac043860e7d 100644 --- a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs +++ b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs @@ -258,6 +258,24 @@ private bool ContainsPlaceholderScope(BoundValuePlaceholderBase placeholder) public override BoundNode? Visit(BoundNode? node) { #if DEBUG + TrackVisit(node); +#endif + return base.Visit(node); + } + +#if DEBUG + protected override void BeforeVisitingSkippedBoundBinaryOperatorChildren(BoundBinaryOperator node) + { + TrackVisit(node); + } + + protected override void BeforeVisitingSkippedBoundCallChildren(BoundCall node) + { + TrackVisit(node); + } + + private void TrackVisit(BoundNode? node) + { if (node is BoundValuePlaceholderBase placeholder) { Debug.Assert(ContainsPlaceholderScope(placeholder)); @@ -267,14 +285,11 @@ private bool ContainsPlaceholderScope(BoundValuePlaceholderBase placeholder) if (_visited is { } && _visited.Count <= MaxTrackVisited) { bool added = _visited.Add(expr); - Debug.Assert(added); + Debug.Assert(added, $"Expression {expr} `{expr.Syntax}` visited more than once."); } } -#endif - return base.Visit(node); } -#if DEBUG private void AssertVisited(BoundExpression expr) { if (expr is BoundValuePlaceholderBase placeholder) @@ -283,7 +298,7 @@ private void AssertVisited(BoundExpression expr) } else if (_visited is { } && _visited.Count <= MaxTrackVisited) { - Debug.Assert(_visited.Contains(expr)); + Debug.Assert(_visited.Contains(expr), $"Expected {expr} `{expr.Syntax}` to be visited."); } } #endif @@ -659,13 +674,6 @@ protected override void VisitArguments(BoundCall node) _localScopeDepth, _diagnostics); } - -#if DEBUG - if (_visited is { } && _visited.Count <= MaxTrackVisited) - { - _visited.Add(node); - } -#endif } private void GetInterpolatedStringPlaceholders( diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs index a8ed41a6f003c..8f0df23b1e56c 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs @@ -114,6 +114,7 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator var binary = (BoundBinaryOperator)node.Left; + BeforeVisitingSkippedBoundBinaryOperatorChildren(binary); rightOperands.Push(binary.Right); BoundExpression current = binary.Left; @@ -121,6 +122,7 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator while (current.Kind == BoundKind.BinaryOperator) { binary = (BoundBinaryOperator)current; + BeforeVisitingSkippedBoundBinaryOperatorChildren(binary); rightOperands.Push(binary.Right); current = binary.Left; } @@ -136,6 +138,10 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator return null; } + protected virtual void BeforeVisitingSkippedBoundBinaryOperatorChildren(BoundBinaryOperator node) + { + } + public sealed override BoundNode? VisitCall(BoundCall node) { if (node.ReceiverOpt is BoundCall receiver1) @@ -147,10 +153,13 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator node = receiver1; while (node.ReceiverOpt is BoundCall receiver2) { + BeforeVisitingSkippedBoundCallChildren(node); calls.Push(node); node = receiver2; } + BeforeVisitingSkippedBoundCallChildren(node); + VisitReceiver(node); do @@ -170,6 +179,10 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator return null; } + protected virtual void BeforeVisitingSkippedBoundCallChildren(BoundCall node) + { + } + /// /// Called only for the first (in evaluation order) in the chain. /// diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 6da08b9784005..0cd3ee133280d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -7871,6 +7871,37 @@ ref struct S Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(47, 16)); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] + public void UserDefinedBinaryOperator_RefStruct_Nested() + { + var source = """ + class C + { + S M() + { + S s; + s = default(S) + 100 + 200; + return s; + } + } + + ref struct S + { + public static S operator+(S y, in int x) => throw null; + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (6,13): error CS8347: Cannot use a result of 'S.operator +(S, in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope + // s = default(S) + 100 + 200; + Diagnostic(ErrorCode.ERR_EscapeCall, "default(S) + 100").WithArguments("S.operator +(S, in int)", "x").WithLocation(6, 13), + // (6,13): error CS8347: Cannot use a result of 'S.operator +(S, in int)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope + // s = default(S) + 100 + 200; + Diagnostic(ErrorCode.ERR_EscapeCall, "default(S) + 100 + 200").WithArguments("S.operator +(S, in int)", "y").WithLocation(6, 13), + // (6,26): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // s = default(S) + 100 + 200; + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "100").WithLocation(6, 26)); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")] public void UserDefinedBinaryOperator_RefStruct_Scoped_Left() { @@ -8531,5 +8562,16 @@ static R F2() // return new R(1) | new R(2); Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(18, 22)); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72873")] + public void Utf8Addition() + { + var code = """ + using System; + ReadOnlySpan x = "Hello"u8 + " "u8 + "World!"u8; + Console.WriteLine(x.Length); + """; + CreateCompilation(code, targetFramework: TargetFramework.Net70).VerifyDiagnostics(); + } } } From ce99f63cf9392fe4612fefee68f65c0968bf999c Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Tue, 23 Apr 2024 10:49:15 +0200 Subject: [PATCH 225/292] Remove mention of removed option `bootstrapConfig` (#73174) --- docs/contributing/Bootstrap Builds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing/Bootstrap Builds.md b/docs/contributing/Bootstrap Builds.md index 52e1a638cc877..128a4d59fe740 100644 --- a/docs/contributing/Bootstrap Builds.md +++ b/docs/contributing/Bootstrap Builds.md @@ -104,5 +104,5 @@ https://github.com/dotnet/roslyn/blob/d73d31cbccb9aa850f3582afb464b709fef88fd7/s Next just run the bootstrap build locally, wait for the `Debug.Assert` to trigger which pops up a dialog. From there you can attach to the VBCSCompiler process and debug through the problem ```cmd -> Build.cmd -bootstrap -bootstrapConfiguration Debug +> Build.cmd -bootstrap ``` From 26687e18643d66378614f03b6c6c8d1eb6381218 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 07:56:32 -0700 Subject: [PATCH 226/292] Port improvements --- .../Protocol/DefaultCapabilitiesProvider.cs | 10 -- .../AbstractDocumentPullDiagnosticHandler.cs | 15 ++- .../AbstractPullDiagnosticHandler.cs | 13 +- ...AbstractWorkspacePullDiagnosticsHandler.cs | 5 +- .../AggregatedDocumentDiagnosticSource.cs | 114 +++++++++--------- .../DiagnosticSourceManager.cs | 18 +-- .../IDiagnosticSourceManager.cs | 2 +- .../DocumentPullDiagnosticHandler.cs | 20 ++- .../PublicDocumentPullDiagnosticsHandler.cs | 29 ++--- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 24 +--- .../PublicWorkspacePullDiagnosticsHandler.cs | 10 +- ...cePullDiagnosticsHandler_IOnInitialized.cs | 14 ++- .../WorkspacePullDiagnosticHandler.cs | 11 +- 13 files changed, 122 insertions(+), 163 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs index 2b2b974ac5a7c..75876a8c924b0 100644 --- a/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs +++ b/src/Features/LanguageServer/Protocol/DefaultCapabilitiesProvider.cs @@ -120,16 +120,6 @@ public ServerCapabilities GetCapabilities(ClientCapabilities clientCapabilities) // Using VS server capabilities because we have our own custom client. capabilities.OnAutoInsertProvider = new VSInternalDocumentOnAutoInsertOptions { TriggerCharacters = ["'", "/", "\n"] }; - if (!supportsVsExtensions) - { - capabilities.DiagnosticOptions = new DiagnosticOptions - { - InterFileDependencies = true, - WorkDoneProgress = true, - WorkspaceDiagnostics = true, - }; - } - return capabilities; } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index dc20144b584f1..243e31c1fe547 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -2,17 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Microsoft.CommonLanguageServerProtocol.Framework; using Roslyn.LanguageServer.Protocol; -using LSP = Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractDocumentPullDiagnosticHandler( IDiagnosticAnalyzerService diagnosticAnalyzerService, IDiagnosticsRefresher diagnosticRefresher, + IDiagnosticSourceManager diagnosticSourceManager, IGlobalOptionService globalOptions) : AbstractPullDiagnosticHandler( diagnosticAnalyzerService, @@ -20,5 +24,12 @@ internal abstract class AbstractDocumentPullDiagnosticHandler where TDiagnosticsParams : IPartialResultParams { - public abstract LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); + protected readonly IDiagnosticSourceManager DiagnosticSourceManager = diagnosticSourceManager; + + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) + { + return DiagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, isDocument: true, cancellationToken); + } + + public abstract TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index e0f1e9de55a38..53c4aeb7224b1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -67,8 +67,6 @@ protected AbstractPullDiagnosticHandler( GlobalOptions = globalOptions; } - protected virtual string? GetDiagnosticSourceIdentifier(TDiagnosticsParams diagnosticsParams) => null; - /// /// Retrieve the previous results we reported. Used so we can avoid resending data for unchanged files. Also /// used so we can report which documents were removed and can have all their diagnostics cleared. @@ -79,7 +77,7 @@ protected AbstractPullDiagnosticHandler( /// Returns all the documents that should be processed in the desired order to process them in. /// protected abstract ValueTask> GetOrderedDiagnosticSourcesAsync( - TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken); + TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken); /// /// Creates the appropriate LSP type to report a new set of diagnostics and resultId. @@ -105,7 +103,7 @@ protected abstract ValueTask> GetOrderedDiagno /// protected abstract DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource); - protected abstract string? GetDiagnosticCategory(TDiagnosticsParams diagnosticsParams); + protected abstract string GetRequestDiagnosticCategory(TDiagnosticsParams diagnosticsParams); /// /// Used by public workspace pull diagnostics to allow it to keep the connection open until @@ -133,9 +131,8 @@ protected virtual Task WaitForChangesAsync(RequestContext context, CancellationT Contract.ThrowIfNull(context.Solution); var clientCapabilities = context.GetRequiredClientCapabilities(); - var category = GetDiagnosticCategory(diagnosticsParams) ?? ""; - var sourceIdentifier = GetDiagnosticSourceIdentifier(diagnosticsParams) ?? ""; - var handlerName = $"{this.GetType().Name}(category: {category}, source: {sourceIdentifier})"; + var category = GetRequestDiagnosticCategory(diagnosticsParams); + var handlerName = $"{this.GetType().Name}(category: {category})"; context.TraceInformation($"{handlerName} started getting diagnostics"); var versionedCache = _categoryToVersionedCache.GetOrAdd(handlerName, static handlerName => new(handlerName)); @@ -159,7 +156,7 @@ protected virtual Task WaitForChangesAsync(RequestContext context, CancellationT // Next process each file in priority order. Determine if diagnostics are changed or unchanged since the // last time we notified the client. Report back either to the client so they can update accordingly. var orderedSources = await GetOrderedDiagnosticSourcesAsync( - diagnosticsParams, context, cancellationToken).ConfigureAwait(false); + diagnosticsParams, category, context, cancellationToken).ConfigureAwait(false); context.TraceInformation($"Processing {orderedSources.Length} documents"); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index 4e97b9b6231e0..95a40d3a70f1d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -51,10 +51,9 @@ public void Dispose() _workspaceRegistrationService.LspSolutionChanged -= OnLspSolutionChanged; } - protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { - var sourceName = GetDiagnosticCategory(diagnosticsParams); - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, sourceName, false, cancellationToken); + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, false, cancellationToken); } private void OnLspSolutionChanged(object? sender, WorkspaceChangeEventArgs e) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs index d12148e32eb62..6a1038dc34229 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs @@ -1,65 +1,65 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. +//// See the LICENSE file in the project root for more information. -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.PooledObjects; +//using System.Collections.Generic; +//using System.Collections.Immutable; +//using System.Diagnostics; +//using System.Threading; +//using System.Threading.Tasks; +//using Microsoft.CodeAnalysis.Diagnostics; +//using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +//using Microsoft.CodeAnalysis.PooledObjects; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +//namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -/// -/// Aggregates multiple source diagnostics -/// -internal sealed class AggregatedDocumentDiagnosticSource : AbstractDocumentDiagnosticSource -{ - private readonly IDiagnosticSourceManager _diagnosticSourceManager; - private readonly string? _sourceName; +///// +///// Aggregates multiple source diagnostics +///// +//internal sealed class AggregatedDocumentDiagnosticSource : AbstractDocumentDiagnosticSource +//{ +// private readonly IDiagnosticSourceManager _diagnosticSourceManager; +// private readonly string? _sourceName; - public AggregatedDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, - TextDocument document, string? sourceName) : base(document) - { - _diagnosticSourceManager = diagnosticSourceManager; - _sourceName = sourceName; - } +// public AggregatedDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, +// TextDocument document, string? sourceName) : base(document) +// { +// _diagnosticSourceManager = diagnosticSourceManager; +// _sourceName = sourceName; +// } - public override bool IsLiveSource() => true; +// public override bool IsLiveSource() => true; - public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var diagnostics); - foreach (var name in GetSourceNames()) - { - var sources = await _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, name, true, cancellationToken).ConfigureAwait(false); - foreach (var source in sources) - { - Debug.Assert(source.IsLiveSource(), "All document sources should be live"); - var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); - diagnostics.AddRange(namedDiagnostics); - } - } +// public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) +// { +// using var _ = ArrayBuilder.GetInstance(out var diagnostics); +// foreach (var name in GetSourceNames()) +// { +// var sources = await _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, name, true, cancellationToken).ConfigureAwait(false); +// foreach (var source in sources) +// { +// Debug.Assert(source.IsLiveSource(), "All document sources should be live"); +// var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); +// diagnostics.AddRange(namedDiagnostics); +// } +// } - return diagnostics.ToImmutableAndClear(); - } +// return diagnostics.ToImmutableAndClear(); +// } - private IEnumerable GetSourceNames() - { - if (this._sourceName != null) - { - yield return this._sourceName; - } - else - { - foreach (var name in _diagnosticSourceManager.GetSourceNames(isDocument: true)) - { - if (name != PullDiagnosticCategories.Task) - yield return name; - } - } - } -} +// private IEnumerable GetSourceNames() +// { +// if (this._sourceName != null) +// { +// yield return this._sourceName; +// } +// else +// { +// foreach (var name in _diagnosticSourceManager.GetSourceNames(isDocument: true)) +// { +// if (name != PullDiagnosticCategories.Task) +// yield return name; +// } +// } +// } +//} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 448332f73132e..ac68762bfc7af 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -39,20 +39,10 @@ public IEnumerable GetSourceNames(bool isDocument) => (isDocument ? _documentProviders : _workspaceProviders).Keys; /// - public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken) + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) { var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; - if (sourceName != null) - { - if (providersDictionary.TryGetValue(sourceName, out var provider)) - return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); - } - else if (isDocument) - { - if (context.TextDocument is { } document) - return new([new AggregatedDocumentDiagnosticSource(this, document, null)]); - } - - return new([]); + Contract.ThrowIfFalse(providersDictionary.TryGetValue(sourceName, out var provider), $"Unrecognized source {sourceName}"); + return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs index b7849dac006e1..3246e5cc9153c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs @@ -27,6 +27,6 @@ internal interface IDiagnosticSourceManager /// Source name. /// True for document sources and false for workspace sources. /// The cancellation token. - ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken); + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index e037834c4bc80..50d97de2263e6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -3,12 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { @@ -16,19 +15,20 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics internal partial class DocumentPullDiagnosticHandler : AbstractDocumentPullDiagnosticHandler { - private readonly IDiagnosticSourceManager _diagnosticSourceManager; public DocumentPullDiagnosticHandler( IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, globalOptions) + : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) { - _diagnosticSourceManager = diagnosticSourceManager; } - protected override string? GetDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.QueryingDiagnosticKind?.Value; + protected override string GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + { + Contract.ThrowIfNull(diagnosticsParams.QueryingDiagnosticKind, "Received a diagnostic request without a source"); + return diagnosticsParams.QueryingDiagnosticKind.Value.Value; + } public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) => diagnosticsParams.TextDocument; @@ -71,12 +71,6 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); - protected override ValueTask> GetOrderedDiagnosticSourcesAsync( - VSInternalDocumentDiagnosticsParams diagnosticsParams, RequestContext context, CancellationToken cancellationToken) - { - return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, diagnosticsParams.QueryingDiagnosticKind?.Value, true, cancellationToken); - } - protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) { return progress.GetFlattenedValues(); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 19db1af4f9add..5149f0ece8f81 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -23,7 +24,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDocumentPullDiagnosticHandler { private readonly IClientLanguageServerManager _clientLanguageServerManager; - private readonly IDiagnosticSourceManager _diagnosticSourceManager; public PublicDocumentPullDiagnosticsHandler( IClientLanguageServerManager clientLanguageServerManager, @@ -31,22 +31,19 @@ public PublicDocumentPullDiagnosticsHandler( IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticsRefresher, globalOptions) + : base(analyzerService, diagnosticsRefresher, diagnosticSourceManager, globalOptions) { - _diagnosticSourceManager = diagnosticSourceManager; _clientLanguageServerManager = clientLanguageServerManager; } - /// - /// Public API doesn't support categories (yet). - /// - protected override string? GetDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) - => null; + protected override string GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) + { + Contract.ThrowIfNull(diagnosticsParams.Identifier, "Received a diagnostic request without an identifier"); + return diagnosticsParams.Identifier; + } public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; - protected override string? GetDiagnosticSourceIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); @@ -90,18 +87,6 @@ protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifi return null; } - protected override ValueTask> GetOrderedDiagnosticSourcesAsync(DocumentDiagnosticParams diagnosticParams, RequestContext context, CancellationToken cancellationToken) - { - if (context.TextDocument is { } document) - { - // Wrap all sources into ISourceProvider so that we can keep using DocumentDiagnosticReport - // (which supports a single source) - return new([new AggregatedDocumentDiagnosticSource(_diagnosticSourceManager, document, diagnosticParams.Identifier)]); - } - - return new([]); - } - protected override ImmutableArray? GetPreviousResults(DocumentDiagnosticParams diagnosticsParams) { if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index 2a52101e93329..407710a01b634 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -17,19 +18,17 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic using DocumentDiagnosticPartialReport = SumType; -internal sealed partial class PublicDocumentPullDiagnosticsHandler// : AbstractDocumentPullDiagnosticHandler, IOnInitialized +internal sealed partial class PublicDocumentPullDiagnosticsHandler : IOnInitialized { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - // Dynamically register a non-local document diagnostic source if Full solution background analysis is enabled - // for analyzer execution. This diagnostic source reports diagnostics in open documents that are reported - // when analyzing other documents or at compilation end. - if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true && IsFsaEnabled()) + // Dynamically register for all of our document diagnostic sources. + if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true) { // TODO: Hookup an option changed handler for changes to BackgroundAnalysisScopeOption // to dynamically register/unregister the non-local document diagnostic source. - var sources = _diagnosticSourceManager.GetSourceNames(isDocument: true); + var sources = DiagnosticSourceManager.GetSourceNames(isDocument: true).Where(source => source != PullDiagnosticCategories.Task); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams() @@ -42,20 +41,9 @@ await _clientLanguageServerManager.SendRequestAsync( Registration FromSourceName(string sourceName) => new() { - Id = sourceName, + Id = Guid.NewGuid().ToString(), Method = Methods.TextDocumentDiagnosticName, RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } }; - - bool IsFsaEnabled() - { - foreach (var language in context.SupportedLanguages) - { - if (GlobalOptions.GetBackgroundAnalysisScope(language) == BackgroundAnalysisScope.FullSolution) - return true; - } - - return false; - } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 013182d9e5bc3..33a380698f57e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -36,11 +36,11 @@ public PublicWorkspacePullDiagnosticsHandler( _clientLanguageServerManager = clientLanguageServerManager; } - /// - /// Public API doesn't support categories (yet). - /// - protected override string? GetDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) - => null; + protected override string GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) + { + Contract.ThrowIfNull(diagnosticsParams.Identifier, "Received a diagnostic request without an identifier"); + return diagnosticsParams.Identifier; + } protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index ed55a6164df3f..478ea62423dbe 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -21,19 +21,21 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ { Registrations = sources.Select(FromSourceName).ToArray() }; - regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics + //regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: regParams, cancellationToken).ConfigureAwait(false); Registration FromSourceName(string sourceName) - => new() { - Id = sourceName, - Method = Methods.WorkspaceDiagnosticName, - RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName } - }; + return new() + { + Id = sourceName, + Method = Methods.WorkspaceDiagnosticName, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName, InterFileDependencies = true, WorkDoneProgress = true } + }; + } } bool IsFsaEnabled() diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 63e9e83e53495..9874e13c8a539 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -24,10 +24,13 @@ internal sealed partial class WorkspacePullDiagnosticHandler( : AbstractWorkspacePullDiagnosticsHandler( workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { - protected override string? GetDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - => diagnosticsParams.QueryingDiagnosticKind?.Value; + protected override string GetRequestDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + { + Contract.ThrowIfNull(diagnosticsParams.QueryingDiagnosticKind, "Received a diagnostic request without a source"); + return diagnosticsParams.QueryingDiagnosticKind.Value.Value; + } -protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) + protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) => [ new VSInternalWorkspaceDiagnosticReport { @@ -67,4 +70,4 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bo } internal override TestAccessor GetTestAccessor() => new(this); -} \ No newline at end of file +} From 66a86a274a7a979b9fe6fabea0ba472de063f6e0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 08:18:53 -0700 Subject: [PATCH 227/292] Remove specialized list pool. --- .../Core/Portable/Serialization/PooledList.cs | 19 ------------------- .../SerializerService_Reference.cs | 13 +++++++------ 2 files changed, 7 insertions(+), 25 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/Serialization/PooledList.cs diff --git a/src/Workspaces/Core/Portable/Serialization/PooledList.cs b/src/Workspaces/Core/Portable/Serialization/PooledList.cs deleted file mode 100644 index 3eb2680bb6702..0000000000000 --- a/src/Workspaces/Core/Portable/Serialization/PooledList.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Collections.Generic; - -namespace Microsoft.CodeAnalysis.Serialization; - -/// -/// This is just internal utility type to reduce allocations and redundant code -/// -internal static class Creator -{ - public static PooledObject> CreateList() - => SharedPools.Default>().GetPooledObject(); -} diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index d81de69768748..031ce659fd857 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -342,10 +342,11 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var metadataKind = (MetadataImageKind)imageKind; if (metadataKind == MetadataImageKind.Assembly) { - using var pooledMetadata = Creator.CreateList(); - using var pooledHandles = Creator.CreateList(); - var count = reader.ReadInt32(); + + var allMetadata = new FixedSizeArrayBuilder(count); + var allHandles = new FixedSizeArrayBuilder(count); + for (var i = 0; i < count; i++) { metadataKind = (MetadataImageKind)reader.ReadInt32(); @@ -353,11 +354,11 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var (metadata, storageHandle) = ReadModuleMetadataFrom(reader, kind, cancellationToken); - pooledMetadata.Object.Add(metadata); - pooledHandles.Object.Add(storageHandle); + allMetadata.Add(metadata); + allHandles.Add(storageHandle); } - return (AssemblyMetadata.Create(pooledMetadata.Object), pooledHandles.Object.ToImmutableArrayOrEmpty()); + return (AssemblyMetadata.Create(allMetadata.MoveToImmutable()), allHandles.MoveToImmutable()); } else { From b34e8631a319ae680fe5d48daaf77fcc5f13e179 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 09:18:12 -0700 Subject: [PATCH 228/292] Restore support for null categories --- .../AbstractDocumentPullDiagnosticHandler.cs | 2 +- .../AbstractPullDiagnosticHandler.cs | 4 +- ...AbstractWorkspacePullDiagnosticsHandler.cs | 2 +- .../AggregatedDocumentDiagnosticSource.cs | 65 ------------------- .../DiagnosticSourceManager.cs | 61 ++++++++++++++++- .../IDiagnosticSourceManager.cs | 2 +- .../DocumentPullDiagnosticHandler.cs | 8 +-- .../PublicDocumentPullDiagnosticsHandler.cs | 10 +-- .../PublicWorkspacePullDiagnosticsHandler.cs | 7 +- .../WorkspacePullDiagnosticHandler.cs | 7 +- 10 files changed, 71 insertions(+), 97 deletions(-) delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index 243e31c1fe547..648dd3e0dc428 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -26,7 +26,7 @@ internal abstract class AbstractDocumentPullDiagnosticHandler> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { return DiagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, isDocument: true, cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 53c4aeb7224b1..85177e1a7fec9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -77,7 +77,7 @@ protected AbstractPullDiagnosticHandler( /// Returns all the documents that should be processed in the desired order to process them in. /// protected abstract ValueTask> GetOrderedDiagnosticSourcesAsync( - TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken); + TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken); /// /// Creates the appropriate LSP type to report a new set of diagnostics and resultId. @@ -103,7 +103,7 @@ protected abstract ValueTask> GetOrderedDiagno /// protected abstract DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource); - protected abstract string GetRequestDiagnosticCategory(TDiagnosticsParams diagnosticsParams); + protected abstract string? GetRequestDiagnosticCategory(TDiagnosticsParams diagnosticsParams); /// /// Used by public workspace pull diagnostics to allow it to keep the connection open until diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index 95a40d3a70f1d..6b6fee7538432 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -51,7 +51,7 @@ public void Dispose() _workspaceRegistrationService.LspSolutionChanged -= OnLspSolutionChanged; } - protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) + protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, false, cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs deleted file mode 100644 index 6a1038dc34229..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AggregatedDocumentDiagnosticSource.cs +++ /dev/null @@ -1,65 +0,0 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. -//// See the LICENSE file in the project root for more information. - -//using System.Collections.Generic; -//using System.Collections.Immutable; -//using System.Diagnostics; -//using System.Threading; -//using System.Threading.Tasks; -//using Microsoft.CodeAnalysis.Diagnostics; -//using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -//using Microsoft.CodeAnalysis.PooledObjects; - -//namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -///// -///// Aggregates multiple source diagnostics -///// -//internal sealed class AggregatedDocumentDiagnosticSource : AbstractDocumentDiagnosticSource -//{ -// private readonly IDiagnosticSourceManager _diagnosticSourceManager; -// private readonly string? _sourceName; - -// public AggregatedDocumentDiagnosticSource(IDiagnosticSourceManager diagnosticSourceManager, -// TextDocument document, string? sourceName) : base(document) -// { -// _diagnosticSourceManager = diagnosticSourceManager; -// _sourceName = sourceName; -// } - -// public override bool IsLiveSource() => true; - -// public override async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) -// { -// using var _ = ArrayBuilder.GetInstance(out var diagnostics); -// foreach (var name in GetSourceNames()) -// { -// var sources = await _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, name, true, cancellationToken).ConfigureAwait(false); -// foreach (var source in sources) -// { -// Debug.Assert(source.IsLiveSource(), "All document sources should be live"); -// var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); -// diagnostics.AddRange(namedDiagnostics); -// } -// } - -// return diagnostics.ToImmutableAndClear(); -// } - -// private IEnumerable GetSourceNames() -// { -// if (this._sourceName != null) -// { -// yield return this._sourceName; -// } -// else -// { -// foreach (var name in _diagnosticSourceManager.GetSourceNames(isDocument: true)) -// { -// if (name != PullDiagnosticCategories.Task) -// yield return name; -// } -// } -// } -//} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index ac68762bfc7af..0482e5c8b9144 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -6,11 +6,15 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -39,10 +43,61 @@ public IEnumerable GetSourceNames(bool isDocument) => (isDocument ? _documentProviders : _workspaceProviders).Keys; /// - public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken) + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken) { var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; - Contract.ThrowIfFalse(providersDictionary.TryGetValue(sourceName, out var provider), $"Unrecognized source {sourceName}"); - return provider.CreateDiagnosticSourcesAsync(context, cancellationToken); + if (sourceName != null) + { + Contract.ThrowIfFalse(providersDictionary.TryGetValue(sourceName, out var provider), $"Unrecognized source {sourceName}"); + return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + } + else + { + // VS Code (and legacy VS ?) pass null sourceName when requesting all sources. + using var _ = ArrayBuilder.GetInstance(out var sourcesBuilder); + foreach (var kvp in providersDictionary) + { + // Exclude task diagnostics from the aggregated sources. + if (kvp.Key != PullDiagnosticCategories.Task) + { + var namedSources = await kvp.Value.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + sourcesBuilder.AddRange(namedSources); + } + } + + var sources = sourcesBuilder.ToImmutableAndClear(); + if (!isDocument || sources.Length <= 1) + { + return sources; + } + else + { + // VS Code document handler (and legacy VS ?) expects a single source for document diagnostics. + // For more details see PublicDocumentPullDiagnosticsHandler.CreateReturn. + Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live"); + return [new AggregatedDocumentDiagnosticSource(sources)]; + } + } + } + + private class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource + { + public bool IsLiveSource() => true; + public Project GetProject() => sources[0].GetProject(); + public ProjectOrDocumentId GetId() => sources[0].GetId(); + public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier(); + public string ToDisplayString() => $"{this.GetType().Name}: count={sources.Length}"; + + public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var diagnostics); + foreach (var source in sources) + { + var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); + diagnostics.AddRange(namedDiagnostics); + } + + return diagnostics.ToImmutableAndClear(); + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs index 3246e5cc9153c..b7849dac006e1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSourceManager.cs @@ -27,6 +27,6 @@ internal interface IDiagnosticSourceManager /// Source name. /// True for document sources and false for workspace sources. /// The cancellation token. - ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string sourceName, bool isDocument, CancellationToken cancellationToken); + ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, string? sourceName, bool isDocument, CancellationToken cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index 50d97de2263e6..f71ed92ae0fbd 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { @@ -24,11 +23,8 @@ public DocumentPullDiagnosticHandler( { } - protected override string GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) - { - Contract.ThrowIfNull(diagnosticsParams.QueryingDiagnosticKind, "Received a diagnostic request without a source"); - return diagnosticsParams.QueryingDiagnosticKind.Value.Value; - } + protected override string? GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) => diagnosticsParams.TextDocument; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 5149f0ece8f81..85ffa72472435 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -4,13 +4,10 @@ using System.Collections.Immutable; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; @@ -36,11 +33,8 @@ public PublicDocumentPullDiagnosticsHandler( _clientLanguageServerManager = clientLanguageServerManager; } - protected override string GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) - { - Contract.ThrowIfNull(diagnosticsParams.Identifier, "Received a diagnostic request without an identifier"); - return diagnosticsParams.Identifier; - } + protected override string? GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) + => diagnosticsParams.Identifier; public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 33a380698f57e..545964f7b2a1a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -36,11 +36,8 @@ public PublicWorkspacePullDiagnosticsHandler( _clientLanguageServerManager = clientLanguageServerManager; } - protected override string GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) - { - Contract.ThrowIfNull(diagnosticsParams.Identifier, "Received a diagnostic request without an identifier"); - return diagnosticsParams.Identifier; - } + protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) + => diagnosticsParams.Identifier; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 9874e13c8a539..e9258826e863f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -24,11 +24,8 @@ internal sealed partial class WorkspacePullDiagnosticHandler( : AbstractWorkspacePullDiagnosticsHandler( workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { - protected override string GetRequestDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - { - Contract.ThrowIfNull(diagnosticsParams.QueryingDiagnosticKind, "Received a diagnostic request without a source"); - return diagnosticsParams.QueryingDiagnosticKind.Value.Value; - } + protected override string? GetRequestDiagnosticCategory(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) => [ From 4244eec1b736800cc408f8c3c7ffebf372de0d79 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 09:47:12 -0700 Subject: [PATCH 229/292] In progress --- .../Diagnostics/IDiagnosticAnalyzerService.cs | 40 ++++++++++--------- .../Diagnostics/DiagnosticAnalyzerService.cs | 14 ++++++- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 4 +- ...AbstractWorkspacePullDiagnosticsHandler.cs | 21 ++++++++-- ...stractWorkspaceDocumentDiagnosticSource.cs | 13 +++--- .../DocumentDiagnosticSource.cs | 6 +-- .../Test.Next/Services/AssetProviderTests.cs | 4 +- .../Services/ServiceHubServicesTests.cs | 2 +- .../Workspace/Solution/StateChecksums.cs | 6 +++ .../Host/RemoteWorkspace.SolutionCreator.cs | 2 +- .../Remote/ServiceHub/Host/TestUtils.cs | 25 ++++++------ 11 files changed, 86 insertions(+), 51 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index a1521848a186c..ecc3404ea591d 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -69,9 +69,11 @@ internal interface IDiagnosticAnalyzerService Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken); /// - /// Get diagnostics of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. - /// Note that for project case, this method returns diagnostics from all project documents as well. Use - /// if you want to fetch only project diagnostics without source locations. + /// Get diagnostics of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned + /// should be up-to-date with respect to the given solution. Note that for project case, this method returns + /// diagnostics from all project documents as well. Use if you + /// want to fetch only project diagnostics without source locations. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -80,22 +82,25 @@ internal interface IDiagnosticAnalyzerService /// Option callback to filter out analyzers to execute for computing diagnostics. /// Indicates if diagnostics suppressed in source via pragmas and SuppressMessageAttributes should be returned. /// - /// Indicates if local document diagnostics must be returned. - /// Local diagnostics are the ones that are reported by analyzers on the same file for which the callback was received - /// and hence can be computed by analyzing a single file in isolation. + /// Indicates if local document diagnostics must be returned. Local diagnostics are the ones that are reported by + /// analyzers on the same file for which the callback was received and hence can be computed by analyzing a single + /// file in isolation. /// /// - /// Indicates if non-local document diagnostics must be returned. - /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback OR - /// in a different file from which the callback was made. Entire project must be analyzed to get the - /// complete set of non-local document diagnostics. + /// Indicates if non-local document diagnostics must be returned. Non-local diagnostics are the ones reported by + /// analyzers either at compilation end callback OR in a different file from which the callback was made. Entire + /// project must be analyzed to get the complete set of non-local document diagnostics. /// - /// Cancellation token. - Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); + Task> GetDiagnosticsForIdsAsync( + Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, + Func? shouldIncludeAnalyzer, DiagnosticKind diagnosticKind, + bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// - /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. - /// Note that this method doesn't return any document diagnostics. Use to also fetch those. + /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from + /// the given solution. all diagnostics returned should be up-to-date with respect to the given solution. Note that + /// this method doesn't return any document diagnostics. Use to also fetch + /// those. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -103,11 +108,10 @@ internal interface IDiagnosticAnalyzerService /// Option callback to filter out analyzers to execute for computing diagnostics. /// Indicates if diagnostics suppressed in source via SuppressMessageAttributes should be returned. /// - /// Indicates if non-local document diagnostics must be returned. - /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback. - /// Entire project must be analyzed to get the complete set of non-local diagnostics. + /// Indicates if non-local document diagnostics must be returned. Non-local diagnostics are the ones reported by + /// analyzers either at compilation end callback. Entire project must be analyzed to get the complete set of + /// non-local diagnostics. /// - /// Cancellation token. Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index aac07fce0e0f2..1739673da3dbf 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -134,10 +134,20 @@ public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken ca } public Task> GetDiagnosticsForIdsAsync( - Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + Solution solution, + ProjectId? projectId, + DocumentId? documentId, + ImmutableHashSet? diagnosticIds, + Func? shouldIncludeAnalyzer, + DiagnosticKind diagnosticKind, + bool includeSuppressedDiagnostics, + bool includeLocalDocumentDiagnostics, + bool includeNonLocalDocumentDiagnostics, + CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + return analyzer.GetDiagnosticsForIdsAsync( + solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, diagnosticKind, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index cb1dc8c68d0b9..1fad5792bf29d 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -21,8 +21,8 @@ public Task> GetCachedDiagnosticsAsync(Solution s public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, DiagnosticKind diagnosticKind, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, diagnosticKind, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index f3911586aad23..b056a182602f3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -78,7 +78,7 @@ protected override async ValueTask> GetOrdered // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). if (category == null || category == PullDiagnosticCategories.WorkspaceDocumentsAndProject) - return await GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken).ConfigureAwait(false); + return await GetDiagnosticSourcesAsync(context, GlobalOptions, category, cancellationToken).ConfigureAwait(false); // if it's a category we don't recognize, return nothing. return []; @@ -158,10 +158,25 @@ private static ImmutableArray GetTaskListDiagnosticSources( /// we have no workspace diagnostics to report and bail out. /// public static async ValueTask> GetDiagnosticSourcesAsync( - RequestContext context, IGlobalOptionService globalOptions, CancellationToken cancellationToken) + RequestContext context, IGlobalOptionService globalOptions, string category, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); + var diagnosticKind = category switch + { + PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, + PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, + PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, + PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, + // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). + null => DiagnosticKind.All, + // if it's a category we don't recognize, return nothing. + _ => (DiagnosticKind?)null, + }; + + if (diagnosticKind is null) + return []; + using var _ = ArrayBuilder.GetInstance(out var result); var solution = context.Solution; @@ -205,7 +220,7 @@ void AddDocumentSources(IEnumerable documents) { // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, shouldIncludeAnalyzer) + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticKind.Value, shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); result.Add(documentDiagnosticSource); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 4cc153fcc9739..7187c68e4cfe9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -12,13 +12,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDocumentDiagnosticSource(TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(document, shouldIncludeAnalyzer); + public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, DiagnosticKind diagnosticKind, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(document, diagnosticKind, shouldIncludeAnalyzer); public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDiagnostics(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(document, codeAnalysisService); - private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, DiagnosticKind diagnosticKind, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { /// @@ -35,7 +35,8 @@ public override async Task> GetDiagnosticsAsync( if (Document is SourceGeneratedDocument sourceGeneratedDocument) { // Unfortunately GetDiagnosticsForIdsAsync returns nothing for source generated documents. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(sourceGeneratedDocument, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); + var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( + sourceGeneratedDocument, range: null, diagnosticKind, includeSuppressedDiagnostics: false, cancellationToken).ConfigureAwait(false); return documentDiagnostics; } else @@ -45,8 +46,8 @@ public override async Task> GetDiagnosticsAsync( // However we can include them as a part of workspace pull when FSA is on. var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( Document.Project.Solution, Document.Project.Id, Document.Id, - diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, - includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); + diagnosticIds: null, shouldIncludeAnalyzer, diagnosticKind, + includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); return documentDiagnostics; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 54f5c6ce5deb2..50e3e06811c1f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -14,8 +14,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public DiagnosticKind DiagnosticKind { get; } = diagnosticKind; - /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. /// @@ -30,11 +28,11 @@ public override async Task> GetDiagnosticsAsync( // GetDiagnosticsForSpanAsync will only run analyzers against the request document. // Also ensure we pass in "includeSuppressedDiagnostics = true" for unnecessary suppressions to be reported. var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( - Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + Document, range: null, diagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); // Add cached Copilot diagnostics when computing analyzer semantic diagnostics. // TODO: move to a separate diagnostic source. https://github.com/dotnet/roslyn/issues/72896 - if (DiagnosticKind == DiagnosticKind.AnalyzerSemantic) + if (diagnosticKind == DiagnosticKind.AnalyzerSemantic) { var copilotDiagnostics = await Document.GetCachedCopilotDiagnosticsAsync(span: null, cancellationToken).ConfigureAwait(false); allSpanDiagnostics = allSpanDiagnostics.AddRange(copilotDiagnostics); diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index 30732fa083aa0..f7424abe817b4 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -76,7 +76,7 @@ public async Task TestAssetSynchronization() // build checksum await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var map = await solution.GetAssetMapAsync(CancellationToken.None); + var map = await solution.GetAssetMapAsync(projectConeId: null, CancellationToken.None); using var remoteWorkspace = CreateRemoteWorkspace(); @@ -104,7 +104,7 @@ public async Task TestSolutionSynchronization() // build checksum await solution.CompilationState.GetChecksumAsync(CancellationToken.None); - var map = await solution.GetAssetMapAsync(CancellationToken.None); + var map = await solution.GetAssetMapAsync(projectConeId: null, CancellationToken.None); using var remoteWorkspace = CreateRemoteWorkspace(); diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 8ec086b01c673..d73b4bb4f6ee9 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -1450,7 +1450,7 @@ private static void VerifyStates(Solution solution1, Solution solution2, string private static async Task VerifyAssetStorageAsync(InProcRemoteHostClient client, Solution solution) { - var map = await solution.GetAssetMapAsync(CancellationToken.None); + var map = await solution.GetAssetMapAsync(projectConeId: null, CancellationToken.None); var storage = client.TestData.WorkspaceManager.SolutionAssetCache; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index dd1c5d79ec793..ccd0717320450 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -486,6 +486,9 @@ public async Task FindAsync( await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } + + public override string ToString() + => $"ProjectStateChecksums({ProjectId})"; } internal sealed class DocumentStateChecksums( @@ -526,6 +529,9 @@ public async Task FindAsync( onAssetFound(Text, text, arg); } } + + public override string ToString() + => $"DocumentStateChecksums({DocumentId})"; } /// diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 5a44050959030..5c23f42a6faa0 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -685,7 +685,7 @@ private async Task ValidateChecksumAsync( var workspace = new AdhocWorkspace(_hostServices); workspace.AddSolution(solutionInfo); - await TestUtils.AssertChecksumsAsync(_assetProvider, checksumFromRequest, workspace.CurrentSolution, incrementalSolutionBuilt).ConfigureAwait(false); + await TestUtils.AssertChecksumsAsync(_assetProvider, checksumFromRequest, workspace.CurrentSolution, incrementalSolutionBuilt, projectConeId).ConfigureAwait(false); } #endif } diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 27047d2158e5b..ada4f5f1be1c1 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -40,43 +40,44 @@ internal static async Task AssertChecksumsAsync( AssetProvider assetService, Checksum checksumFromRequest, Solution solutionFromScratch, - Solution incrementalSolutionBuilt) + Solution incrementalSolutionBuilt, + ProjectId? projectConeId) { #if DEBUG var sb = new StringBuilder(); var allChecksumsFromRequest = await GetAllChildrenChecksumsAsync(checksumFromRequest).ConfigureAwait(false); - var assetMapFromNewSolution = await solutionFromScratch.GetAssetMapAsync(CancellationToken.None).ConfigureAwait(false); - var assetMapFromIncrementalSolution = await incrementalSolutionBuilt.GetAssetMapAsync(CancellationToken.None).ConfigureAwait(false); + var assetMapFromNewSolution = await solutionFromScratch.GetAssetMapAsync(projectConeId, CancellationToken.None).ConfigureAwait(false); + var assetMapFromIncrementalSolution = await incrementalSolutionBuilt.GetAssetMapAsync(projectConeId, CancellationToken.None).ConfigureAwait(false); // check 4 things // 1. first see if we create new solution from scratch, it works as expected (indicating a bug in incremental update) var mismatch1 = assetMapFromNewSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList(); - AppendMismatch(mismatch1, "assets only in new solutoin but not in the request", sb); + AppendMismatch(mismatch1, "Assets only in new solution but not in the request", sb); // 2. second check what items is mismatching for incremental solution var mismatch2 = assetMapFromIncrementalSolution.Where(p => !allChecksumsFromRequest.Contains(p.Key)).ToList(); - AppendMismatch(mismatch2, "assets only in the incremental solution but not in the request", sb); + AppendMismatch(mismatch2, "Assets only in the incremental solution but not in the request", sb); // 3. check whether solution created from scratch and incremental one have any mismatch var mismatch3 = assetMapFromNewSolution.Where(p => !assetMapFromIncrementalSolution.ContainsKey(p.Key)).ToList(); - AppendMismatch(mismatch3, "assets only in new solution but not in incremental solution", sb); + AppendMismatch(mismatch3, "Assets only in new solution but not in incremental solution", sb); var mismatch4 = assetMapFromIncrementalSolution.Where(p => !assetMapFromNewSolution.ContainsKey(p.Key)).ToList(); - AppendMismatch(mismatch4, "assets only in incremental solution but not in new solution", sb); + AppendMismatch(mismatch4, "Assets only in incremental solution but not in new solution", sb); // 4. see what item is missing from request var mismatch5 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromNewSolution.Keys)).ConfigureAwait(false); - AppendMismatch(mismatch5, "assets only in the request but not in new solution", sb); + AppendMismatch(mismatch5, "Assets only in the request but not in new solution", sb); var mismatch6 = await GetAssetFromAssetServiceAsync(allChecksumsFromRequest.Except(assetMapFromIncrementalSolution.Keys)).ConfigureAwait(false); - AppendMismatch(mismatch6, "assets only in the request but not in incremental solution", sb); + AppendMismatch(mismatch6, "Assets only in the request but not in incremental solution", sb); var result = sb.ToString(); if (result.Length > 0) { Logger.Log(FunctionId.SolutionCreator_AssetDifferences, result); - Debug.Fail("Differences detected in solution checksum: " + result); + Debug.Fail($"Differences detected in solution checksum (ProjectId={projectConeId}):\r\n{result}"); } return; @@ -154,10 +155,10 @@ private static void AddAllTo(DocumentStateChecksums documentStateChecksums, Hash /// create checksum to corresponding object map from solution this map should contain every parts of solution /// that can be used to re-create the solution back /// - public static async Task> GetAssetMapAsync(this Solution solution, CancellationToken cancellationToken) + public static async Task> GetAssetMapAsync(this Solution solution, ProjectId? projectConeId, CancellationToken cancellationToken) { var map = new Dictionary(); - await solution.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); + await solution.AppendAssetMapAsync(map, projectConeId, cancellationToken).ConfigureAwait(false); return map; } From 6da89112795daab4deaca6deaec64b7a13568c5e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 09:55:47 -0700 Subject: [PATCH 230/292] Remove diagnostic function not used in any product code --- .../Diagnostics/IDiagnosticAnalyzerService.cs | 16 ---------------- .../Diagnostics/DiagnosticAnalyzerService.cs | 6 ------ ...agnosticIncrementalAnalyzer_GetDiagnostics.cs | 3 --- .../Diagnostics/TestDiagnosticAnalyzerDriver.cs | 8 ++++++-- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index a1521848a186c..f2099d71bf024 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -47,22 +47,6 @@ internal interface IDiagnosticAnalyzerService /// Cancellation token. Task> GetCachedDiagnosticsAsync(Workspace workspace, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); - /// - /// Get diagnostics for the given solution. all diagnostics returned should be up-to-date with respect to the given solution. - /// - /// Solution to fetch diagnostics for. - /// Optional project to scope the returned diagnostics. - /// Optional document to scope the returned diagnostics. - /// Indicates if diagnostics suppressed in source via pragmas and SuppressMessageAttributes should be returned. - /// - /// Indicates if non-local document diagnostics must be returned. - /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback OR - /// in a different file from which the callback was made. Entire project must be analyzed to get the - /// complete set of non-local document diagnostics. - /// - /// Cancellation token. - Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); - /// /// Force analyzes the given project by running all applicable analyzers on the project and caching the reported analyzer diagnostics. /// diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index aac07fce0e0f2..d2d7ff6d55715 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -121,12 +121,6 @@ public Task> GetCachedDiagnosticsAsync(Workspace return analyzer.GetCachedDiagnosticsAsync(workspace.CurrentSolution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } - public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - { - var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsAsync(solution, projectId, documentId, includeSuppressedDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); - } - public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index cb1dc8c68d0b9..8df2a61cd58f2 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -18,9 +18,6 @@ internal partial class DiagnosticIncrementalAnalyzer public Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeCachedDiagnosticGetter(this, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); diff --git a/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs index 8419a40314bd9..44f697772dedd 100644 --- a/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs +++ b/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -55,7 +55,9 @@ private async Task> GetDiagnosticsAsync( if (getDocumentDiagnostics) { var text = await document.GetTextAsync().ConfigureAwait(false); - var dxs = await _diagnosticAnalyzerService.GetDiagnosticsAsync(project.Solution, project.Id, document.Id, _includeSuppressedDiagnostics, _includeNonLocalDocumentDiagnostics, CancellationToken.None); + var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, document.Id, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + _includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); documentDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync( filterSpan is null ? dxs.Where(d => d.DataLocation.DocumentId != null) @@ -66,7 +68,9 @@ filterSpan is null if (getProjectDiagnostics) { - var dxs = await _diagnosticAnalyzerService.GetDiagnosticsAsync(project.Solution, project.Id, documentId: null, _includeSuppressedDiagnostics, _includeNonLocalDocumentDiagnostics, CancellationToken.None); + var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + _includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); projectDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync(dxs.Where(d => d.DocumentId is null), project, CancellationToken.None); } From 00a567d58beb10bd87e0a5de68f2f265bc6584ed Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 10:04:24 -0700 Subject: [PATCH 231/292] in progress --- .../InlineDiagnosticsTaggerProviderTests.cs | 2 +- .../Test/CodeFixes/CodeFixServiceTests.cs | 5 ++- .../DiagnosticAnalyzerServiceTests.cs | 5 ++- .../Diagnostics/DiagnosticProviderTests.vb | 8 ++-- .../Diagnostics/DiagnosticServiceTests.vb | 42 ++++++++----------- .../Squiggles/SquiggleUtilities.cs | 8 +--- .../Squiggles/TestDiagnosticTagProducer.cs | 4 +- .../ExternalDiagnosticUpdateSourceTests.vb | 4 -- 8 files changed, 32 insertions(+), 46 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs b/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs index 339bf5c7e32b2..8462f9b447d79 100644 --- a/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/InlineDiagnostics/InlineDiagnosticsTaggerProviderTests.cs @@ -55,6 +55,6 @@ private static async Task>> GetTag private static async Task>> GetTagSpansAsync(EditorTestWorkspace workspace) { workspace.GlobalOptions.SetGlobalOption(InlineDiagnosticsOptionsStorage.EnableInlineDiagnostics, LanguageNames.CSharp, true); - return (await TestDiagnosticTagProducer.GetDiagnosticsAndErrorSpans(workspace)).Item2; + return await TestDiagnosticTagProducer.GetTagSpansAsync(workspace); } } diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 7cafd90ce14eb..46fd1f86dcc0e 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -1079,8 +1079,9 @@ void M() ? root.DescendantNodes().OfType().First().Span : root.DescendantNodes().OfType().First().Span; - await diagnosticIncrementalAnalyzer.GetDiagnosticsAsync( - sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, includeSuppressedDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); + await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( + sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + includeSuppressedDiagnostics: true, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); var lowPriorityAnalyzers = new ConcurrentSet(); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 9fb93970e27cf..58c1dce218eb7 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -71,8 +71,9 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var analyzer = service.CreateIncrementalAnalyzer(workspace); var globalOptions = exportProvider.GetExportedValue(); - var diagnostics = await analyzer.GetDiagnosticsAsync( - workspace.CurrentSolution, projectId: null, documentId: null, includeSuppressedDiagnostics: false, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); + var diagnostics = await analyzer.GetDiagnosticsForIdsAsync( + workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); Assert.NotEmpty(diagnostics); } diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb index 479f3b4014a6b..df768f0d7279d 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb @@ -261,11 +261,9 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Next Dim diagnosticProvider = GetDiagnosticProvider(workspace) - Dim actualDiagnostics = diagnosticProvider.GetDiagnosticsAsync( - workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None).Result + Dim actualDiagnostics = diagnosticProvider.GetDiagnosticsForIdsAsync( + workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None).Result If diagnostics Is Nothing Then Assert.Equal(0, actualDiagnostics.Length) diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 1f9aec9da2ced..307c1437ddec0 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -536,10 +536,9 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, root.FullSpan) Assert.Equal(0, diagnostics.Count()) - diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, projectId:=Nothing, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None).ConfigureAwait(False) + diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Dim diagnostic = diagnostics.First() Assert.True(diagnostic.Id = "AD0001") Assert.Contains("CodeBlockStartedAnalyzer", diagnostic.Message, StringComparison.Ordinal) @@ -608,10 +607,9 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Assert.Equal(1, descriptorsMap.Count) Dim document = project.Documents.Single() - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, diagnostics.Count()) Dim diagnostic = diagnostics.First() Assert.Equal(OperationAnalyzer.Descriptor.Id, diagnostic.Id) @@ -951,10 +949,9 @@ class AnonymousFunctions ' Verify no duplicate analysis/diagnostics. Dim document = project.Documents.Single() - Dim diagnostics = (Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None)). + Dim diagnostics = (Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None)). Select(Function(d) d.Id = NamedTypeAnalyzer.DiagDescriptor.Id) Assert.Equal(1, diagnostics.Count) @@ -1047,10 +1044,9 @@ class AnonymousFunctions ' Verify project diagnostics contains diagnostics reported on both partial definitions. Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(2, diagnostics.Count()) Dim file1HasDiag = False, file2HasDiag = False For Each diagnostic In diagnostics @@ -2151,10 +2147,9 @@ class MyClass Assert.Equal(expectedCount, diagnostics.Count()) ' Get diagnostics explicitly - Dim hiddenDiagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim hiddenDiagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, hiddenDiagnostics.Count()) Assert.Equal(analyzer.Descriptor.Id, hiddenDiagnostics.Single().Id) End Using @@ -2239,10 +2234,9 @@ class C Assert.Equal(1, descriptorsMap.Count) Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id, documentId:=Nothing, - includeSuppressedDiagnostics:=False, - includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(0, diagnostics.Count()) End Using End Function diff --git a/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs b/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs index 1a76d41a4f352..90ed3e6ea22e0 100644 --- a/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs +++ b/src/EditorFeatures/TestUtilities/Squiggles/SquiggleUtilities.cs @@ -28,7 +28,7 @@ public static class SquiggleUtilities internal static TestComposition WpfCompositionWithSolutionCrawler = EditorTestCompositions.EditorFeaturesWpf .RemoveParts(typeof(MockWorkspaceEventListenerProvider)); - internal static async Task<(ImmutableArray, ImmutableArray>)> GetDiagnosticsAndErrorSpansAsync( + internal static async Task>> GetTagSpansAsync( EditorTestWorkspace workspace, IReadOnlyDictionary> analyzerMap = null) where TProvider : AbstractDiagnosticsTaggerProvider @@ -43,14 +43,10 @@ public static class SquiggleUtilities using var disposable = tagger as IDisposable; await wrapper.WaitForTags(); - var service = (DiagnosticAnalyzerService)workspace.ExportProvider.GetExportedValue(); - var analyzerDiagnostics = await service.GetDiagnosticsAsync(workspace.CurrentSolution, - projectId: null, documentId: null, includeSuppressedDiagnostics: false, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); - var snapshot = textBuffer.CurrentSnapshot; var spans = tagger.GetTags(snapshot.GetSnapshotSpanCollection()).ToImmutableArray(); - return (analyzerDiagnostics, spans); + return spans; } } } diff --git a/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs b/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs index 79108121d640b..d42a8b5abcc4d 100644 --- a/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs +++ b/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs @@ -18,11 +18,11 @@ internal sealed class TestDiagnosticTagProducer where TProvider : AbstractDiagnosticsTaggerProvider where TTag : class, ITag { - internal static Task<(ImmutableArray, ImmutableArray>)> GetDiagnosticsAndErrorSpans( + internal static Task>> GetTagSpansAsync( EditorTestWorkspace workspace, IReadOnlyDictionary>? analyzerMap = null) { - return SquiggleUtilities.GetDiagnosticsAndErrorSpansAsync(workspace, analyzerMap); + return SquiggleUtilities.GetTagSpansAsync(workspace, analyzerMap); } internal static DiagnosticData CreateDiagnosticData(EditorTestHostDocument document, TextSpan span) diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 0c278d44d7ece..7b9b26917d379 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -430,10 +430,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function - Public Function GetDiagnosticsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, includeSuppressedDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsAsync - Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() - End Function - Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function From 79df2f740f286eb81e3239a1d6bfdc40d6a3e8e8 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 10:06:14 -0700 Subject: [PATCH 232/292] clean up --- .../AlwaysActivateInProcLanguageClient.cs | 20 ---- .../Portable/Diagnostics/DiagnosticKind.cs | 1 - ...stractWorkspaceDiagnosticSourceProvider.cs | 1 - ...ExportDiagnosticSourceProviderAttribute.cs | 20 ---- .../DiagnosticSources/IDiagnosticSource.cs | 2 +- ...EditAndContinueDiagnosticSourceProvider.cs | 3 +- .../DocumentPullDiagnosticHandler.cs | 101 +++++++++--------- ...ntaxAndSemanticDiagnosticSourceProvider.cs | 9 +- .../DocumentTaskDiagnosticSourceProvider.cs | 3 +- ...ocumentNonLocalDiagnosticSourceProvider.cs | 3 +- .../PublicDocumentPullDiagnosticsHandler.cs | 3 +- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 8 +- ...PublicWorkspaceDiagnosticSourceProvider.cs | 25 ----- ...cePullDiagnosticsHandler_IOnInitialized.cs | 1 - ...mentsAndProjectDiagnosticSourceProvider.cs | 3 +- ...EditAndContinueDiagnosticSourceProvider.cs | 3 +- .../WorkspaceTaskDiagnosticSourceProvider.cs | 3 +- .../Contracts/HotReloadDocumentDiagnostics.cs | 4 +- .../Contracts/IHotReloadDiagnosticManager.cs | 4 +- ...stractHotReloadDiagnosticSourceProvider.cs | 3 +- ...cumentHotReloadDiagnosticSourceProvider.cs | 4 +- .../Internal/HotReloadDiagnosticManager.cs | 8 +- ...kspaceHotReloadDiagnosticSourceProvider.cs | 3 +- .../InternalAPI.Unshipped.txt | 4 +- .../Xaml/Internal/XamlDiagnosticSource.cs | 3 +- .../Internal/XamlDiagnosticSourceProvider.cs | 5 +- 26 files changed, 78 insertions(+), 169 deletions(-) delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index 4ffb0938af9b0..8c8921a532a49 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -81,26 +81,6 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa { SupportsMultipleContextsDiagnostics = true, DiagnosticKinds = diagnosticSourceNames.Select(n => new VSInternalDiagnosticKind(n)).ToArray(), - //[ - // // Needs to be MEF driven - - // // Support a specialized requests dedicated to task-list items. This way the client can ask just - // // for these, independently of other diagnostics. They can also throttle themselves to not ask if - // // the task list would not be visible. - // new(PullDiagnosticCategories.Task), - // new(PullDiagnosticCategories.EditAndContinue), - // // Dedicated request for workspace-diagnostics only. We will only respond to these if FSA is on. - // new(PullDiagnosticCategories.WorkspaceDocumentsAndProject), - // // Fine-grained diagnostics requests. Importantly, this separates out syntactic vs semantic - // // requests, allowing the former to quickly reach the user without blocking on the latter. In a - // // similar vein, compiler diagnostics are explicitly distinct from analyzer-diagnostics, allowing - // // the former to appear as soon as possible as they are much more critical for the user and should - // // not be delayed by a slow analyzer. - // new(PullDiagnosticCategories.DocumentCompilerSyntax), - // new(PullDiagnosticCategories.DocumentCompilerSemantic), - // new(PullDiagnosticCategories.DocumentAnalyzerSyntax), - // new(PullDiagnosticCategories.DocumentAnalyzerSemantic), - //], BuildOnlyDiagnosticIds = _buildOnlyDiagnostics .SelectMany(lazy => lazy.Metadata.BuildOnlyDiagnostics) .Distinct() diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs index 92e43a3d0e51a..bc97619a79475 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticKind.cs @@ -13,5 +13,4 @@ internal enum DiagnosticKind CompilerSemantic = 2, AnalyzerSyntax = 3, AnalyzerSemantic = 4, - EditAndContinue = 5, } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs index 473aecf7010d5..4a2312e0e198d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs @@ -71,4 +71,3 @@ protected static bool ShouldSkipDocument(RequestContext context, TextDocument do return document.IsRazorDocument(); } } - diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs deleted file mode 100644 index 2c165e3d7ca29..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/ExportDiagnosticSourceProviderAttribute.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; - -/// -/// Use this attribute to declare a implementation for inclusion in a MEF-based workspace. -/// -/// -/// Declares a implementation for inclusion in a MEF-based workspace. -/// -[MetadataAttribute] -[AttributeUsage(AttributeTargets.Class)] -internal class ExportDiagnosticSourceProviderAttribute() : ExportAttribute(typeof(IDiagnosticSourceProvider)) -{ -} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs index 242be19b8d8b6..e8cb70bc1de4c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/IDiagnosticSource.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; /// Wrapper around a source for diagnostics (e.g. a or ) /// so that we can share per file diagnostic reporting code in /// -internal interface IDiagnosticSource // we'll need one for XHR errors +internal interface IDiagnosticSource { Project GetProject(); ProjectOrDocumentId GetId(); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs index 52aa70dcab7a0..76e37b53b4d2b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs @@ -9,11 +9,10 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentEditAndContinueDiagnosticSourceProvider() diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index f71ed92ae0fbd..16ee2f87d2bf2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -8,68 +8,67 @@ using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +[Method(VSInternalMethods.DocumentPullDiagnosticName)] +internal partial class DocumentPullDiagnosticHandler + : AbstractDocumentPullDiagnosticHandler { - [Method(VSInternalMethods.DocumentPullDiagnosticName)] - internal partial class DocumentPullDiagnosticHandler - : AbstractDocumentPullDiagnosticHandler + public DocumentPullDiagnosticHandler( + IDiagnosticAnalyzerService analyzerService, + IDiagnosticSourceManager diagnosticSourceManager, + IDiagnosticsRefresher diagnosticRefresher, + IGlobalOptionService globalOptions) + : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) { - public DocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService analyzerService, - IDiagnosticSourceManager diagnosticSourceManager, - IDiagnosticsRefresher diagnosticRefresher, - IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) - { - } - - protected override string? GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.QueryingDiagnosticKind?.Value; - - public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) - => diagnosticsParams.TextDocument; + } - protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - => [ - new VSInternalDiagnosticReport - { - Diagnostics = diagnostics, - ResultId = resultId, - Identifier = DocumentDiagnosticIdentifier, - // Mark these diagnostics as superseding any diagnostics for the same document from the - // WorkspacePullDiagnosticHandler. We are always getting completely accurate and up to date diagnostic - // values for a particular file, so our results should always be preferred over the workspace-pull - // values which are cached and may be out of date. - Supersedes = WorkspaceDiagnosticIdentifier, - } - ]; + protected override string? GetRequestDiagnosticCategory(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.QueryingDiagnosticKind?.Value; - protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); + public override TextDocumentIdentifier? GetTextDocumentIdentifier(VSInternalDocumentDiagnosticsParams diagnosticsParams) + => diagnosticsParams.TextDocument; - protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) - { - report = CreateReport(identifier, diagnostics: null, resultId); - return true; - } - - protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) - { - if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) + protected override VSInternalDiagnosticReport[] CreateReport(TextDocumentIdentifier identifier, Roslyn.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) + => [ + new VSInternalDiagnosticReport { - return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); + Diagnostics = diagnostics, + ResultId = resultId, + Identifier = DocumentDiagnosticIdentifier, + // Mark these diagnostics as superseding any diagnostics for the same document from the + // WorkspacePullDiagnosticHandler. We are always getting completely accurate and up to date diagnostic + // values for a particular file, so our results should always be preferred over the workspace-pull + // values which are cached and may be out of date. + Supersedes = WorkspaceDiagnosticIdentifier, } + ]; - // The client didn't provide us with a previous result to look for, so we can't lookup anything. - return null; - } + protected override VSInternalDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); - protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) - => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, out VSInternalDiagnosticReport[] report) + { + report = CreateReport(identifier, diagnostics: null, resultId); + return true; + } - protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) + protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) + { + if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) { - return progress.GetFlattenedValues(); + return ImmutableArray.Create(new PreviousPullResult(diagnosticsParams.PreviousResultId, diagnosticsParams.TextDocument)); } + + // The client didn't provide us with a previous result to look for, so we can't lookup anything. + return null; + } + + protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) + => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); + + protected override VSInternalDiagnosticReport[]? CreateReturn(BufferedProgress progress) + { + return progress.GetFlattenedValues(); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs index a33f882167d5e..436465a8f351d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -34,7 +33,7 @@ public override ValueTask> CreateDiagnosticSou return new([source]); } - [ExportDiagnosticSourceProvider, Shared] + [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) @@ -43,7 +42,7 @@ internal sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] ID { } - [ExportDiagnosticSourceProvider, Shared] + [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) @@ -52,7 +51,7 @@ internal sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] { } - [ExportDiagnosticSourceProvider, Shared] + [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) @@ -61,7 +60,7 @@ internal sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] ID { } - [ExportDiagnosticSourceProvider, Shared] + [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs index 8a343fabb4899..c020ee7e30e81 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs @@ -8,12 +8,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs index 171fa9bb9bd55..876f058999c8d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -9,13 +9,12 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.SolutionCrawler; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 85ffa72472435..5368045e550da 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -36,7 +36,8 @@ public PublicDocumentPullDiagnosticsHandler( protected override string? GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; - public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; + public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) + => diagnosticsParams.TextDocument; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) => ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index 407710a01b634..f2a7244eeeec8 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -6,17 +6,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; - -using DocumentDiagnosticReport = SumType; - // A document diagnostic partial report is defined as having the first literal send = DocumentDiagnosticReport (aka changed / unchanged) followed // by n DocumentDiagnosticPartialResult literals. // See https://github.com/microsoft/vscode-languageserver-node/blob/main/protocol/src/common/proposed.diagnostics.md#textDocument_diagnostic -using DocumentDiagnosticPartialReport = SumType; internal sealed partial class PublicDocumentPullDiagnosticsHandler : IOnInitialized { @@ -29,11 +24,12 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ // to dynamically register/unregister the non-local document diagnostic source. var sources = DiagnosticSourceManager.GetSourceNames(isDocument: true).Where(source => source != PullDiagnosticCategories.Task); + var registrations = sources.Select(FromSourceName).ToArray(); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams() { - Registrations = sources.Select(FromSourceName).ToArray() + Registrations = registrations }, cancellationToken).ConfigureAwait(false); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs deleted file mode 100644 index c7e98b6187713..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspaceDiagnosticSourceProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; - -// FIGURE OUT WHAT IT IS SUPPOSED TO DO -/* -[ExportDiagnosticSourceProvider, Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class PublicWorkspaceDiagnosticSourceProvider( - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, - [Import] IGlobalOptionService globalOptions) - : AbstractWorkspaceDiagnosticSourceProvider("") -{ -} -*/ diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index 478ea62423dbe..a87be284174f0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -21,7 +21,6 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ { Registrations = sources.Select(FromSourceName).ToArray() }; - //regParams.Registrations = []; // DISABLE FOR NOW; VS Code does not support workspace diagnostics await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: regParams, diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs index 8c36d0905616c..38e94f79aceaa 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.SolutionCrawler; @@ -18,7 +17,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs index 8869ef4ccccfa..cb8ff971b769b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs @@ -9,12 +9,11 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.EditAndContinue) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs index c5c18f4163c3f..32b09034eebe2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.TaskList; @@ -16,7 +15,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs index 6f948ddfaef3d..13e899beb1c4c 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs @@ -6,9 +6,9 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts { - internal class HotReloadDocumentDiagnostics(DocumentId documentId, ImmutableArray errors) + internal class HotReloadDocumentDiagnostics(DocumentId documentId, ImmutableArray diagnostics) { public DocumentId DocumentId => documentId; - public ImmutableArray Errors => errors; + public ImmutableArray Diagnostics => diagnostics; } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs index 3e601bb176a98..29d9562916cdc 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs @@ -14,12 +14,12 @@ internal interface IHotReloadDiagnosticManager ImmutableArray Sources { get; } /// - /// Registers source of hot reload diagnostics. + /// Registers source of hot reload diagnostics. Callers are responsible for refreshing diagnostics after registration. /// void Register(IHotReloadDiagnosticSource source); /// - /// Unregisters source of hot reload diagnostics. + /// Unregisters source of hot reload diagnostics. Callers are responsible for refreshing diagnostics after un-registration. /// void Unregister(IHotReloadDiagnosticSource source); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs index dcc71a2714646..832c1c6aa7c97 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs @@ -13,8 +13,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; internal abstract class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider { - string IDiagnosticSourceProvider.Name => "HotReloadDiagnostic"; - + string IDiagnosticSourceProvider.Name => "HotReloadDiagnostics"; bool IDiagnosticSourceProvider.IsDocument => throw new NotImplementedException(); ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) => throw new NotImplementedException(); diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs index 16d9e109d4741..f568fffbc8a43 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs @@ -6,17 +6,15 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadDiagnosticManager) diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs index cdd278715f947..b045e419d3eb7 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -25,15 +25,9 @@ void IHotReloadDiagnosticManager.Register(IHotReloadDiagnosticSource source) { // We use array instead of e.g. HashSet because we expect the number of sources to be small. Usually 1. if (!_sources.Contains(source)) - { _sources = _sources.Add(source); - } } void IHotReloadDiagnosticManager.Unregister(IHotReloadDiagnosticSource source) - { - // We use array instead of e.g. HashSet because we expect the number of sources to be small. Usually 1. - _sources = _sources.Remove(source); - diagnosticsRefresher.RequestWorkspaceRefresh(); - } + => _sources = _sources.Remove(source); } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs index 9648b2902a8c3..c147997aac46d 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs @@ -12,12 +12,11 @@ using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadErrorService) diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 439b114134052..368b69575bb21 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,8 +1,8 @@ const Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.SourceName = "HotReloadDiagnostic" -> string! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.Diagnostics.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.DocumentId.get -> Microsoft.CodeAnalysis.DocumentId! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.Errors.get -> System.Collections.Immutable.ImmutableArray -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.HotReloadDocumentDiagnostics(Microsoft.CodeAnalysis.DocumentId! documentId, System.Collections.Immutable.ImmutableArray errors) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.HotReloadDocumentDiagnostics(Microsoft.CodeAnalysis.DocumentId! documentId, System.Collections.Immutable.ImmutableArray diagnostics) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Refresh() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Register(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs index 439572e17b058..1cee9dbe1b173 100644 --- a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSource.cs @@ -16,12 +16,11 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; internal sealed class XamlDiagnosticSource(IXamlDiagnosticSource xamlDiagnosticSource, TextDocument document) : IDiagnosticSource { + bool IDiagnosticSource.IsLiveSource() => true; Project IDiagnosticSource.GetProject() => document.Project; ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id); - TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {document.FilePath ?? document.Name} in {document.Project.Name}"; - bool IDiagnosticSource.IsLiveSource() => true; async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { diff --git a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs index d9b7b043cfe1d..110413bfdb9ef 100644 --- a/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/Xaml/Internal/XamlDiagnosticSourceProvider.cs @@ -10,18 +10,17 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; namespace Microsoft.CodeAnalysis.ExternalAccess.Xaml; -[ExportDiagnosticSourceProvider, Shared] +[Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class XamlDiagnosticSourceProvider([Import(AllowDefault = true)] IXamlDiagnosticSource? xamlDiagnosticSource) : IDiagnosticSourceProvider { bool IDiagnosticSourceProvider.IsDocument => true; - string IDiagnosticSourceProvider.Name => "XamlDiagnosticSource"; + string IDiagnosticSourceProvider.Name => "XamlDiagnostics"; ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { From 943038c0158dfde69b45e634e59261a4c1daef77 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 10:11:29 -0700 Subject: [PATCH 233/292] Fix --- .../Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs | 2 +- .../Core/Test/Venus/DocumentService_IntegrationTests.vb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 58c1dce218eb7..150d2ad7fc279 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -73,7 +73,7 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var diagnostics = await analyzer.GetDiagnosticsForIdsAsync( workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, - includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); + includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); Assert.NotEmpty(diagnostics); } diff --git a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb index 0f6d62a5a75da..6338718d65607 100644 --- a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb +++ b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb @@ -238,9 +238,9 @@ class { } diagnosticService.CreateIncrementalAnalyzer(workspace) ' confirm that IDE doesn't report the diagnostics - Dim diagnostics = Await diagnosticService.GetDiagnosticsAsync(workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, - includeSuppressedDiagnostics:=False, includeNonLocalDocumentDiagnostics:=True, - CancellationToken.None) + Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( + workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, diagnosticIds:=Nothing, Function(a) True, + includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.False(diagnostics.Any()) End Using End Function From 6af117c46ce34ad0b6358c3a2e59b9987c331d2b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 10:14:29 -0700 Subject: [PATCH 234/292] Simplify --- src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs | 2 +- .../Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs | 2 +- .../TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 46fd1f86dcc0e..1bf99ecb4ba35 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -1080,7 +1080,7 @@ void M() : root.DescendantNodes().OfType().First().Span; await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( - sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics: true, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 150d2ad7fc279..e734ac991f1ab 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -72,7 +72,7 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var globalOptions = exportProvider.GetExportedValue(); var diagnostics = await analyzer.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); Assert.NotEmpty(diagnostics); } diff --git a/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs index 44f697772dedd..f5767ebb02d68 100644 --- a/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs +++ b/src/Features/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -56,7 +56,7 @@ private async Task> GetDiagnosticsAsync( { var text = await document.GetTextAsync().ConfigureAwait(false); var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, document.Id, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + project.Solution, project.Id, document.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, _includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); documentDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync( filterSpan is null @@ -69,7 +69,7 @@ filterSpan is null if (getProjectDiagnostics) { var dxs = await _diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: _ => true, + project.Solution, project.Id, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, _includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, _includeNonLocalDocumentDiagnostics, CancellationToken.None); projectDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync(dxs.Where(d => d.DocumentId is null), project, CancellationToken.None); } From 903bdeb94590ea97ae78266f619cc3b4e61e9f41 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 10:15:17 -0700 Subject: [PATCH 235/292] Simplify --- .../Test2/Diagnostics/DiagnosticProviderTests.vb | 2 +- .../Test2/Diagnostics/DiagnosticServiceTests.vb | 12 ++++++------ .../Test/Venus/DocumentService_IntegrationTests.vb | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb index df768f0d7279d..19a9f764f34be 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb @@ -262,7 +262,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim diagnosticProvider = GetDiagnosticProvider(workspace) Dim actualDiagnostics = diagnosticProvider.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + workspace.CurrentSolution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None).Result If diagnostics Is Nothing Then diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 307c1437ddec0..d6722a2eee3cf 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -537,7 +537,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Assert.Equal(0, diagnostics.Count()) diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, projectId:=Nothing, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Dim diagnostic = diagnostics.First() Assert.True(diagnostic.Id = "AD0001") @@ -608,7 +608,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim document = project.Documents.Single() Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, diagnostics.Count()) Dim diagnostic = diagnostics.First() @@ -950,7 +950,7 @@ class AnonymousFunctions ' Verify no duplicate analysis/diagnostics. Dim document = project.Documents.Single() Dim diagnostics = (Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None)). Select(Function(d) d.Id = NamedTypeAnalyzer.DiagDescriptor.Id) @@ -1045,7 +1045,7 @@ class AnonymousFunctions ' Verify project diagnostics contains diagnostics reported on both partial definitions. Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(2, diagnostics.Count()) Dim file1HasDiag = False, file2HasDiag = False @@ -2148,7 +2148,7 @@ class MyClass ' Get diagnostics explicitly Dim hiddenDiagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(1, hiddenDiagnostics.Count()) Assert.Equal(analyzer.Descriptor.Id, hiddenDiagnostics.Single().Id) @@ -2235,7 +2235,7 @@ class C Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, Function(a) True, + project.Solution, project.Id, documentId:=Nothing, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.Equal(0, diagnostics.Count()) End Using diff --git a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb index 6338718d65607..25ca0b50529f7 100644 --- a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb +++ b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb @@ -239,7 +239,7 @@ class { } ' confirm that IDE doesn't report the diagnostics Dim diagnostics = Await diagnosticService.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, diagnosticIds:=Nothing, Function(a) True, + workspace.CurrentSolution, projectId:=Nothing, documentId:=document.Id, diagnosticIds:=Nothing, shouldIncludeAnalyzer:=Nothing, includeSuppressedDiagnostics:=False, includeLocalDocumentDiagnostics:=True, includeNonLocalDocumentDiagnostics:=True, CancellationToken.None) Assert.False(diagnostics.Any()) End Using From cacc40fa365726ca4b6ee1a7d462c31ed6018fde Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 10:17:27 -0700 Subject: [PATCH 236/292] revert --- .../Diagnostics/IDiagnosticAnalyzerService.cs | 40 +++++++++---------- .../Diagnostics/DiagnosticAnalyzerService.cs | 14 +------ ...osticIncrementalAnalyzer_GetDiagnostics.cs | 4 +- ...AbstractWorkspacePullDiagnosticsHandler.cs | 21 ++-------- ...stractWorkspaceDocumentDiagnosticSource.cs | 13 +++--- .../DocumentDiagnosticSource.cs | 6 ++- 6 files changed, 35 insertions(+), 63 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index ecc3404ea591d..a1521848a186c 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -69,11 +69,9 @@ internal interface IDiagnosticAnalyzerService Task ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken); /// - /// Get diagnostics of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned - /// should be up-to-date with respect to the given solution. Note that for project case, this method returns - /// diagnostics from all project documents as well. Use if you - /// want to fetch only project diagnostics without source locations. + /// Get diagnostics of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. + /// Note that for project case, this method returns diagnostics from all project documents as well. Use + /// if you want to fetch only project diagnostics without source locations. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -82,25 +80,22 @@ internal interface IDiagnosticAnalyzerService /// Option callback to filter out analyzers to execute for computing diagnostics. /// Indicates if diagnostics suppressed in source via pragmas and SuppressMessageAttributes should be returned. /// - /// Indicates if local document diagnostics must be returned. Local diagnostics are the ones that are reported by - /// analyzers on the same file for which the callback was received and hence can be computed by analyzing a single - /// file in isolation. + /// Indicates if local document diagnostics must be returned. + /// Local diagnostics are the ones that are reported by analyzers on the same file for which the callback was received + /// and hence can be computed by analyzing a single file in isolation. /// /// - /// Indicates if non-local document diagnostics must be returned. Non-local diagnostics are the ones reported by - /// analyzers either at compilation end callback OR in a different file from which the callback was made. Entire - /// project must be analyzed to get the complete set of non-local document diagnostics. + /// Indicates if non-local document diagnostics must be returned. + /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback OR + /// in a different file from which the callback was made. Entire project must be analyzed to get the + /// complete set of non-local document diagnostics. /// - Task> GetDiagnosticsForIdsAsync( - Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, - Func? shouldIncludeAnalyzer, DiagnosticKind diagnosticKind, - bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); + /// Cancellation token. + Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// - /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from - /// the given solution. all diagnostics returned should be up-to-date with respect to the given solution. Note that - /// this method doesn't return any document diagnostics. Use to also fetch - /// those. + /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. + /// Note that this method doesn't return any document diagnostics. Use to also fetch those. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -108,10 +103,11 @@ Task> GetDiagnosticsForIdsAsync( /// Option callback to filter out analyzers to execute for computing diagnostics. /// Indicates if diagnostics suppressed in source via SuppressMessageAttributes should be returned. /// - /// Indicates if non-local document diagnostics must be returned. Non-local diagnostics are the ones reported by - /// analyzers either at compilation end callback. Entire project must be analyzed to get the complete set of - /// non-local diagnostics. + /// Indicates if non-local document diagnostics must be returned. + /// Non-local diagnostics are the ones reported by analyzers either at compilation end callback. + /// Entire project must be analyzed to get the complete set of non-local diagnostics. /// + /// Cancellation token. Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index 1739673da3dbf..aac07fce0e0f2 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -134,20 +134,10 @@ public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken ca } public Task> GetDiagnosticsForIdsAsync( - Solution solution, - ProjectId? projectId, - DocumentId? documentId, - ImmutableHashSet? diagnosticIds, - Func? shouldIncludeAnalyzer, - DiagnosticKind diagnosticKind, - bool includeSuppressedDiagnostics, - bool includeLocalDocumentDiagnostics, - bool includeNonLocalDocumentDiagnostics, - CancellationToken cancellationToken) + Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync( - solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, diagnosticKind, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 1fad5792bf29d..cb1dc8c68d0b9 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -21,8 +21,8 @@ public Task> GetCachedDiagnosticsAsync(Solution s public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds: null, shouldIncludeAnalyzer: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, DiagnosticKind diagnosticKind, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, diagnosticKind, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index b056a182602f3..f3911586aad23 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -78,7 +78,7 @@ protected override async ValueTask> GetOrdered // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). if (category == null || category == PullDiagnosticCategories.WorkspaceDocumentsAndProject) - return await GetDiagnosticSourcesAsync(context, GlobalOptions, category, cancellationToken).ConfigureAwait(false); + return await GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken).ConfigureAwait(false); // if it's a category we don't recognize, return nothing. return []; @@ -158,25 +158,10 @@ private static ImmutableArray GetTaskListDiagnosticSources( /// we have no workspace diagnostics to report and bail out. /// public static async ValueTask> GetDiagnosticSourcesAsync( - RequestContext context, IGlobalOptionService globalOptions, string category, CancellationToken cancellationToken) + RequestContext context, IGlobalOptionService globalOptions, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); - var diagnosticKind = category switch - { - PullDiagnosticCategories.DocumentCompilerSyntax => DiagnosticKind.CompilerSyntax, - PullDiagnosticCategories.DocumentCompilerSemantic => DiagnosticKind.CompilerSemantic, - PullDiagnosticCategories.DocumentAnalyzerSyntax => DiagnosticKind.AnalyzerSyntax, - PullDiagnosticCategories.DocumentAnalyzerSemantic => DiagnosticKind.AnalyzerSemantic, - // if this request doesn't have a category at all (legacy behavior, assume they're asking about everything). - null => DiagnosticKind.All, - // if it's a category we don't recognize, return nothing. - _ => (DiagnosticKind?)null, - }; - - if (diagnosticKind is null) - return []; - using var _ = ArrayBuilder.GetInstance(out var result); var solution = context.Solution; @@ -220,7 +205,7 @@ void AddDocumentSources(IEnumerable documents) { // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticKind.Value, shouldIncludeAnalyzer) + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); result.Add(documentDiagnosticSource); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 7187c68e4cfe9..4cc153fcc9739 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -12,13 +12,13 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDocumentDiagnosticSource(TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, DiagnosticKind diagnosticKind, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(document, diagnosticKind, shouldIncludeAnalyzer); + public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(document, shouldIncludeAnalyzer); public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDiagnostics(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(document, codeAnalysisService); - private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, DiagnosticKind diagnosticKind, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { /// @@ -35,8 +35,7 @@ public override async Task> GetDiagnosticsAsync( if (Document is SourceGeneratedDocument sourceGeneratedDocument) { // Unfortunately GetDiagnosticsForIdsAsync returns nothing for source generated documents. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( - sourceGeneratedDocument, range: null, diagnosticKind, includeSuppressedDiagnostics: false, cancellationToken).ConfigureAwait(false); + var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(sourceGeneratedDocument, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); return documentDiagnostics; } else @@ -46,8 +45,8 @@ public override async Task> GetDiagnosticsAsync( // However we can include them as a part of workspace pull when FSA is on. var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( Document.Project.Solution, Document.Project.Id, Document.Id, - diagnosticIds: null, shouldIncludeAnalyzer, diagnosticKind, - includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); + diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, + includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); return documentDiagnostics; } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 50e3e06811c1f..54f5c6ce5deb2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -14,6 +14,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { + public DiagnosticKind DiagnosticKind { get; } = diagnosticKind; + /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. /// @@ -28,11 +30,11 @@ public override async Task> GetDiagnosticsAsync( // GetDiagnosticsForSpanAsync will only run analyzers against the request document. // Also ensure we pass in "includeSuppressedDiagnostics = true" for unnecessary suppressions to be reported. var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( - Document, range: null, diagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + Document, range: null, diagnosticKind: this.DiagnosticKind, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); // Add cached Copilot diagnostics when computing analyzer semantic diagnostics. // TODO: move to a separate diagnostic source. https://github.com/dotnet/roslyn/issues/72896 - if (diagnosticKind == DiagnosticKind.AnalyzerSemantic) + if (DiagnosticKind == DiagnosticKind.AnalyzerSemantic) { var copilotDiagnostics = await Document.GetCachedCopilotDiagnosticsAsync(span: null, cancellationToken).ConfigureAwait(false); allSpanDiagnostics = allSpanDiagnostics.AddRange(copilotDiagnostics); From 620ff14cad76bd6e93e18022b07ae7c2d3bdc2ca Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 10:23:40 -0700 Subject: [PATCH 237/292] Fix build breaks --- .../Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs | 1 - .../DiagnosticSources/AbstractProjectDiagnosticSource.cs | 4 ++-- .../Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs | 1 - .../Public/PublicWorkspacePullDiagnosticsHandler.cs | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs index 4a2312e0e198d..0316bd9b08909 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.Host; using Roslyn.Utilities; - namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDiagnosticSourceProvider(string name) : IDiagnosticSourceProvider diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs index b2efe993483c6..ac49b0a9da7c6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs @@ -33,7 +33,7 @@ public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(P : null; public string ToDisplayString() => Project.Name; - private sealed class FullSolutionAnalysisDiagnosticSource(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) : AbstractProjectDiagnosticSource(project) { /// @@ -55,7 +55,7 @@ public override async Task> GetDiagnosticsAsync( } } - private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) + private sealed class CodeAnalysisDiagnosticSource(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) : AbstractProjectDiagnosticSource(project) { /// diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 0a11ff5f1127f..e9a5ad49650bf 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -10,7 +10,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - internal sealed class DocumentDiagnosticSource(IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 545964f7b2a1a..edab103a15ec6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -36,7 +36,7 @@ public PublicWorkspacePullDiagnosticsHandler( _clientLanguageServerManager = clientLanguageServerManager; } - protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) + protected override string? GetRequestDiagnosticCategory(WorkspaceDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) From a3212557dd2684f9021a88d7f4a3214d69f74563 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 10:46:08 -0700 Subject: [PATCH 238/292] More build break fixes --- .../PublicDocumentPullDiagnosticsHandler.cs | 2 +- .../WorkspacePullDiagnosticHandler.cs | 44 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 5368045e550da..426148bed2f11 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -36,7 +36,7 @@ public PublicDocumentPullDiagnosticsHandler( protected override string? GetRequestDiagnosticCategory(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.Identifier; - public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) + public override TextDocumentIdentifier GetTextDocumentIdentifier(DocumentDiagnosticParams diagnosticsParams) => diagnosticsParams.TextDocument; protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index e9258826e863f..62f6e9b5b8332 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -40,31 +40,31 @@ protected override VSInternalWorkspaceDiagnosticReport[] CreateReport(TextDocume } ]; -protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) - => CreateReport(identifier, diagnostics: null, resultId: null); + protected override VSInternalWorkspaceDiagnosticReport[] CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); -protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) -{ - // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of - // these and we can save a lot of memory not serializing/deserializing all of this. - report = null; - return false; -} + protected override bool TryCreateUnchangedReport(TextDocumentIdentifier identifier, string resultId, [NotNullWhen(true)] out VSInternalWorkspaceDiagnosticReport[]? report) + { + // Skip reporting 'unchanged' document reports for workspace pull diagnostics. There are often a ton of + // these and we can save a lot of memory not serializing/deserializing all of this. + report = null; + return false; + } -protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) - => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); + protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) + => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); -protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) -{ - // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics - // produced by document diagnostics. - return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); -} + protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool isLiveSource) + { + // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics + // produced by document diagnostics. + return ConvertTags(diagnosticData, isLiveSource, potentialDuplicate: true); + } -protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) -{ - return progress.GetFlattenedValues(); -} + protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) + { + return progress.GetFlattenedValues(); + } -internal override TestAccessor GetTestAccessor() => new(this); + internal override TestAccessor GetTestAccessor() => new(this); } From f322dbf705d4dce09f9780b188a58925a4113b47 Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Tue, 23 Apr 2024 11:35:32 -0700 Subject: [PATCH 239/292] Add Semantic Search feature flag to pkgdef --- src/VisualStudio/Core/Def/PackageRegistration.pkgdef | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index 97c945e244fa0..25767a4f03745 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -39,6 +39,12 @@ "Title"="Run C#/VB code analysis with ServerGC (requires restart)" "PreviewPaneChannels"="IntPreview,int.main" +[$RootKey$\FeatureFlags\Roslyn\SemanticSearchEnabled] +"Description"="Enable C# Semantic Search." +"Value"=dword:00000000 +"Title"="Enable C# Semantic Search" +"PreviewPaneChannels"="IntPreview,int.main" + // Corresponds to WellKnownExperimentNames.LspPullDiagnosticsFeatureFlag [$RootKey$\FeatureFlags\Lsp\PullDiagnostics] "Description"="Enables the LSP-powered diagnostics for managed .Net projects" From 2c0d648f37a53a893ae4bd1b75406e7720c9d8aa Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 12:01:08 -0700 Subject: [PATCH 240/292] Remove parameter which was always passed the same value --- .../DiagnosticIncrementalAnalyzer.Executor.cs | 73 ++----------------- ...alyzer.InProcOrRemoteHostAnalyzerRunner.cs | 3 +- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 2 +- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 2 +- ...alStudioDiagnosticAnalyzerExecutorTests.cs | 12 +-- 5 files changed, 15 insertions(+), 77 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 05b432309c757..68cdf1724c25b 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -24,7 +24,7 @@ internal partial class DiagnosticIncrementalAnalyzer /// Return all diagnostics that belong to given project for the given StateSets (analyzers) either from cache or by calculating them /// private async Task GetProjectAnalysisDataAsync( - CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, bool forceAnalyzerRun, CancellationToken cancellationToken) + CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, project, stateSets, cancellationToken)) { @@ -42,64 +42,7 @@ private async Task GetProjectAnalysisDataAsync( return existingData; } - // PERF: Check whether we want to analyze this project or not. - var fullAnalysisEnabled = GlobalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullAnalysisEnabled, out var analyzersFullAnalysisEnabled); - if (forceAnalyzerRun) - { - // We are forcing full solution analysis for all diagnostics. - fullAnalysisEnabled = true; - compilerFullAnalysisEnabled = true; - analyzersFullAnalysisEnabled = true; - } - - if (!fullAnalysisEnabled) - { - Logger.Log(FunctionId.Diagnostics_ProjectDiagnostic, p => $"FSA off ({p.FilePath ?? p.Name})", project); - - // If we are producing document diagnostics for some other document in this project, we still want to show - // certain project-level diagnostics that would cause file-level diagnostics to be broken. We will only do this though if - // some file that's open is depending on this project though -- that way we're going to only be analyzing projects - // that have already had compilations produced for. - var shouldProduceOutput = false; - - var projectDependencyGraph = project.Solution.GetProjectDependencyGraph(); - - foreach (var openDocumentId in project.Solution.Workspace.GetOpenDocumentIds()) - { - if (openDocumentId.ProjectId == project.Id || projectDependencyGraph.DoesProjectTransitivelyDependOnProject(openDocumentId.ProjectId, project.Id)) - { - shouldProduceOutput = true; - break; - } - } - - var results = ImmutableDictionary.Empty; - - if (shouldProduceOutput) - { - (results, _) = await UpdateWithDocumentLoadAndGeneratorFailuresAsync( - results, - project, - version, - cancellationToken).ConfigureAwait(false); - } - - return new ProjectAnalysisData(project.Id, VersionStamp.Default, existingData.Result, results); - } - - // Reduce the state sets to analyze based on individual full solution analysis values - // for compiler diagnostics and analyzers. - if (!compilerFullAnalysisEnabled) - { - Debug.Assert(analyzersFullAnalysisEnabled); - stateSets = stateSets.WhereAsArray(s => !s.Analyzer.IsCompilerAnalyzer()); - } - else if (!analyzersFullAnalysisEnabled) - { - stateSets = stateSets.WhereAsArray(s => s.Analyzer.IsCompilerAnalyzer() || s.Analyzer.IsWorkspaceDiagnosticAnalyzer()); - } - - var result = await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideOptions, stateSets, forceAnalyzerRun, existingData.Result, cancellationToken).ConfigureAwait(false); + var result = await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideOptions, stateSets, existingData.Result, cancellationToken).ConfigureAwait(false); // If project is not loaded successfully, get rid of any semantic errors from compiler analyzer. // Note: In the past when project was not loaded successfully we did not run any analyzers on the project. @@ -169,7 +112,7 @@ private static async Task private async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzers? compilationWithAnalyzers, Project project, ImmutableArray ideAnalyzers, bool forcedAnalysis, CancellationToken cancellationToken) + CompilationWithAnalyzers? compilationWithAnalyzers, Project project, ImmutableArray ideAnalyzers, CancellationToken cancellationToken) { try { @@ -179,8 +122,8 @@ private async Task 0) { // calculate regular diagnostic analyzers diagnostics - var resultMap = await _diagnosticAnalyzerRunner.AnalyzeProjectAsync(project, compilationWithAnalyzers, - forcedAnalysis, logPerformanceInfo: false, getTelemetryInfo: true, cancellationToken).ConfigureAwait(false); + var resultMap = await _diagnosticAnalyzerRunner.AnalyzeProjectAsync( + project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: true, cancellationToken).ConfigureAwait(false); result = resultMap.AnalysisResult; @@ -198,7 +141,7 @@ private async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, bool forcedAnalysis, + CompilationWithAnalyzers? compilationWithAnalyzers, Project project, IdeAnalyzerOptions ideOptions, ImmutableArray stateSets, ImmutableDictionary existing, CancellationToken cancellationToken) { try @@ -225,12 +168,12 @@ await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync( compilationWithAnalyzers.AnalysisOptions.ReportSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - var result = await ComputeDiagnosticsAsync(compilationWithReducedAnalyzers, project, ideAnalyzers, forcedAnalysis, cancellationToken).ConfigureAwait(false); + var result = await ComputeDiagnosticsAsync(compilationWithReducedAnalyzers, project, ideAnalyzers, cancellationToken).ConfigureAwait(false); return MergeExistingDiagnostics(version, existing, result); } // we couldn't reduce the set. - return await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideAnalyzers, forcedAnalysis, cancellationToken).ConfigureAwait(false); + return await ComputeDiagnosticsAsync(compilationWithAnalyzers, project, ideAnalyzers, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs index f829aa1582150..88214593ae6d4 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs @@ -50,12 +50,11 @@ public Task> AnalyzeProjectAsync( Project project, CompilationWithAnalyzers compilationWithAnalyzers, - bool forceExecuteAllAnalyzers, bool logPerformanceInfo, bool getTelemetryInfo, CancellationToken cancellationToken) => AnalyzeAsync(documentAnalysisScope: null, project, compilationWithAnalyzers, - isExplicit: false, forceExecuteAllAnalyzers, logPerformanceInfo, getTelemetryInfo, cancellationToken); + isExplicit: false, forceExecuteAllAnalyzers: true, logPerformanceInfo, getTelemetryInfo, cancellationToken); private async Task> AnalyzeAsync( DocumentAnalysisScope? documentAnalysisScope, diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index cb1dc8c68d0b9..5534f2fe22c94 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -269,7 +269,7 @@ protected override async Task AppendDiagnosticsAsync(Project project, IEnumerabl // unlike the suppressed (disabled) analyzer, we will include hidden diagnostic only analyzers here. var compilation = await CreateCompilationWithAnalyzersAsync(project, ideOptions, stateSets, IncludeSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - var result = await Owner.GetProjectAnalysisDataAsync(compilation, project, ideOptions, stateSets, forceAnalyzerRun: true, cancellationToken).ConfigureAwait(false); + var result = await Owner.GetProjectAnalysisDataAsync(compilation, project, ideOptions, stateSets, cancellationToken).ConfigureAwait(false); foreach (var stateSet in stateSets) { diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index af943468881a4..e7054c9de2c94 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -37,7 +37,7 @@ public async Task> ForceAnalyzeProjectAsync(Proje compilationWithAnalyzers = await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync(project, ideOptions, activeAnalyzers, includeSuppressedDiagnostics: true, cancellationToken).ConfigureAwait(false); - var result = await GetProjectAnalysisDataAsync(compilationWithAnalyzers, project, ideOptions, stateSets, forceAnalyzerRun: true, cancellationToken).ConfigureAwait(false); + var result = await GetProjectAnalysisDataAsync(compilationWithAnalyzers, project, ideOptions, stateSets, cancellationToken).ConfigureAwait(false); using var _ = ArrayBuilder.GetInstance(out var diagnostics); diff --git a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs index 51c11425410a8..4eaec828a85b0 100644 --- a/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/VisualStudioDiagnosticAnalyzerExecutorTests.cs @@ -192,11 +192,7 @@ void Method() analyzerReference.GetAnalyzers(project.Language).Where(a => a.GetType() == analyzerType).ToImmutableArray(), new WorkspaceAnalyzerOptions(project.AnalyzerOptions, ideAnalyzerOptions)); - // no result for open file only analyzer unless forced - var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, forceExecuteAllAnalyzers: false, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); - Assert.Empty(result.AnalysisResult); - - result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, forceExecuteAllAnalyzers: true, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); + var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); var analyzerResult = result.AnalysisResult[compilationWithAnalyzers.Analyzers[0]]; // check result @@ -234,7 +230,7 @@ void Method() var compilationWithAnalyzers = (await project.GetCompilationAsync()) .WithAnalyzers(analyzers, new WorkspaceAnalyzerOptions(project.AnalyzerOptions, ideAnalyzerOptions)); - var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, forceExecuteAllAnalyzers: false, + var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None); var analyzerResult = result.AnalysisResult[compilationWithAnalyzers.Analyzers[0]]; @@ -258,8 +254,8 @@ private static async Task AnalyzeAsync(TestWorkspace w analyzerReference.GetAnalyzers(project.Language).Where(a => a.GetType() == analyzerType).ToImmutableArray(), new WorkspaceAnalyzerOptions(project.AnalyzerOptions, ideOptions)); - var result = await executor.AnalyzeProjectAsync(project, analyzerDriver, forceExecuteAllAnalyzers: true, logPerformanceInfo: false, - getTelemetryInfo: false, cancellationToken); + var result = await executor.AnalyzeProjectAsync( + project, analyzerDriver, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken); return result.AnalysisResult[analyzerDriver.Analyzers[0]]; } From 28a348df21e1ff173fa206aa86fb4042486e2614 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 12:13:06 -0700 Subject: [PATCH 241/292] Fix test regressions --- .../Handler/Diagnostics/AbstractPullDiagnosticHandler.cs | 2 -- .../Diagnostics/AbstractPullDiagnosticTestsBase.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 85177e1a7fec9..94bb3f537e1c9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -37,8 +37,6 @@ internal abstract partial class AbstractPullDiagnosticHandler protected const int WorkspaceDiagnosticIdentifier = 1; protected const int DocumentDiagnosticIdentifier = 2; - // internal for testing purposes - internal const int DocumentNonLocalDiagnosticIdentifier = 3; private readonly IDiagnosticsRefresher _diagnosticRefresher; protected readonly IGlobalOptionService GlobalOptions; diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index 201f98f4233cf..73231c4dc1bf1 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -303,7 +303,7 @@ static DocumentDiagnosticParams CreateProposedDocumentDiagnosticParams( { return new DocumentDiagnosticParams { - Identifier = testNonLocalDiagnostics ? DocumentPullDiagnosticHandler.DocumentNonLocalDiagnosticIdentifier.ToString() : null, + Identifier = testNonLocalDiagnostics ? PublicDocumentNonLocalDiagnosticSourceProvider.NonLocal : null, PreviousResultId = previousResultId, PartialResultToken = progress, TextDocument = vsTextDocumentIdentifier, From 01f0b909f5d41cf60b20e69cd6fc12833a7ac14d Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Tue, 23 Apr 2024 13:35:22 -0700 Subject: [PATCH 242/292] Address unstable ordering in AbstractBuiltInCodeStyleDiagnosticAnalyzer (#73189) * Address unstable ordering in AbstractBuiltInCodeStyleDiagnosticAnalyzer This class was previously non-deterministic about the value that it would assign to Descriptor based on it's ctor input. The process it previously used was to take the ImmutableDictionary it took into it's ctor and convert the keys from that dictionary into an immutable aray. From that, it would use the first item as the descrptor. This would lead to unstable values being used, as evidenced in https://github.com/dotnet/roslyn/issues/73070. Additionally, as DiagnosticDescriptor's IEquatable didn't use CustomTags to differentiate between descriptors, I saw that several items being added to the dictionary by some callers were being duplicated to each other. As part of this PR, I changed the AbstractBuiltInCodeStyleDiagnosticAnalyzer to no longer take in an ImmutableDictionary, but rather an ImmutableArray with tuples of the keys/values. This allows the caller to control a stable ordering of descriptors. --- ...placedUsingDirectivesDiagnosticAnalyzer.cs | 11 ++-- ...eCollectionExpressionDiagnosticAnalyzer.cs | 14 ++--- .../UseExpressionBodyDiagnosticAnalyzer.cs | 11 ++-- ...pressionBodyForLambdaDiagnosticAnalyzer.cs | 8 +-- ...BuiltInCodeStyleDiagnosticAnalyzerTests.cs | 59 +++++++++++++++++++ .../Tests/CSharpAnalyzers.UnitTests.projitems | 4 ++ ...tractBuiltInCodeStyleDiagnosticAnalyzer.cs | 9 ++- ...BuiltInCodeStyleDiagnosticAnalyzer_Core.cs | 3 - ...nUnnecessaryCodeStyleDiagnosticAnalyzer.cs | 8 +-- .../AbstractFileHeaderDiagnosticAnalyzer.cs | 10 ++-- ...edParametersAndValuesDiagnosticAnalyzer.cs | 12 ++-- ...SimplifyTypeNamesDiagnosticAnalyzerBase.cs | 25 ++++---- ...CollectionInitializerDiagnosticAnalyzer.cs | 8 +-- ...tUseObjectInitializerDiagnosticAnalyzer.cs | 8 +-- 14 files changed, 122 insertions(+), 68 deletions(-) create mode 100644 src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs diff --git a/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs index 0295d3a9908cc..7072dff8597cf 100644 --- a/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/MisplacedUsingDirectives/MisplacedUsingDirectivesDiagnosticAnalyzer.cs @@ -3,15 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.CSharp.MisplacedUsingDirectives; @@ -40,9 +37,11 @@ internal sealed class MisplacedUsingDirectivesDiagnosticAnalyzer : AbstractBuilt s_localizableTitle, s_localizableInsideMessage); public MisplacedUsingDirectivesDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_outsideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement) - .Add(s_insideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement)) + : base( + [ + (s_outsideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement), + (s_insideDiagnosticDescriptor, CSharpCodeStyleOptions.PreferredUsingDirectivePlacement) + ]) { } diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs index 7fe59d662f13d..b1daeecb8190a 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.UseCollectionInitializer; @@ -22,18 +21,15 @@ internal abstract class AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer public static readonly ImmutableDictionary ChangesSemantics = ImmutableDictionary.Empty.Add(UseCollectionInitializerHelpers.ChangesSemanticsName, ""); - protected new readonly DiagnosticDescriptor Descriptor; protected readonly DiagnosticDescriptor UnnecessaryCodeDescriptor; protected AbstractCSharpUseCollectionExpressionDiagnosticAnalyzer(string diagnosticId, EnforceOnBuild enforceOnBuild) - : base(ImmutableDictionary.Empty - // Ugly hack. We need to create a descriptor to pass to our base *and* assign to one of our fields. - // The conditional pattern form lets us do that. - .Add(CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: false) is var descriptor ? descriptor : null, CodeStyleOptions2.PreferCollectionExpression) - .Add(CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: true) is var unnecessaryCodeDescriptor ? unnecessaryCodeDescriptor : null, CodeStyleOptions2.PreferCollectionExpression)) + : base( + [ + (CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: false), CodeStyleOptions2.PreferCollectionExpression) + ]) { - Descriptor = descriptor; - UnnecessaryCodeDescriptor = unnecessaryCodeDescriptor; + UnnecessaryCodeDescriptor = CreateDescriptor(diagnosticId, enforceOnBuild, isUnnecessary: true); } private static DiagnosticDescriptor CreateDescriptor(string diagnosticId, EnforceOnBuild enforceOnBuild, bool isUnnecessary) diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs index bed23c5b6d1a6..fa00a7e87a56b 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBody/UseExpressionBodyDiagnosticAnalyzer.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeGeneration; @@ -14,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBody; [DiagnosticAnalyzer(LanguageNames.CSharp)] -internal class UseExpressionBodyDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer +internal sealed class UseExpressionBodyDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer { public const string FixesError = nameof(FixesError); @@ -28,16 +27,16 @@ public UseExpressionBodyDiagnosticAnalyzer() _syntaxKinds = _helpers.SelectManyAsArray(h => h.SyntaxKinds); } - private static ImmutableDictionary GetSupportedDescriptorsWithOptions() + private static ImmutableArray<(DiagnosticDescriptor, IOption2)> GetSupportedDescriptorsWithOptions() { - var builder = ImmutableDictionary.CreateBuilder(); + var builder = new FixedSizeArrayBuilder<(DiagnosticDescriptor, IOption2)>(_helpers.Length); foreach (var helper in _helpers) { var descriptor = CreateDescriptorWithId(helper.DiagnosticId, helper.EnforceOnBuild, hasAnyCodeStyleOption: true, helper.UseExpressionBodyTitle, helper.UseExpressionBodyTitle); - builder.Add(descriptor, helper.Option); + builder.Add((descriptor, helper.Option)); } - return builder.ToImmutable(); + return builder.MoveToImmutable(); } public override DiagnosticAnalyzerCategory GetAnalyzerCategory() diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs index d4248991e4b42..21af7ba3f40bd 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; @@ -21,9 +20,10 @@ internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : AbstractBui private static readonly DiagnosticDescriptor s_useBlockBodyForLambda = CreateDescriptorWithId(UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle, UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle); public UseExpressionBodyForLambdaDiagnosticAnalyzer() : base( - ImmutableDictionary.Empty - .Add(s_useExpressionBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas) - .Add(s_useBlockBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas)) + [ + (s_useExpressionBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas), + (s_useBlockBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas) + ]) { } diff --git a/src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs b/src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs new file mode 100644 index 0000000000000..616ccf33ffd80 --- /dev/null +++ b/src/Analyzers/CSharp/Tests/AbstractBuiltInCodeStyleDiagnosticAnalyzer/AbstractBuiltInCodeStyleDiagnosticAnalyzerTests.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AbstractBuiltInCodeStyleDiagnosticAnalyzer; + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Utilities; +using Xunit; + +public sealed class AbstractBuiltInCodeStyleDiagnosticAnalyzerTests +{ + [Fact] + public void VerifyDiagnosticDescriptorOrderingMaintained() + { + var ids = Enumerable.Range(10, 20).Select(item => "IDE_" + item); + + var analyzer = new TestAnalyzer(ids); + + Assert.Equal(analyzer.SupportedDiagnostics.Select(static diagnostic => diagnostic.Id), ids); + } + + private sealed class TestAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + { + public TestAnalyzer(IEnumerable ids) + : base(CreateSupportedDiagnosticsWithOptionsFromIds(ids)) + { + } + + private static ImmutableArray<(DiagnosticDescriptor, ImmutableHashSet)> CreateSupportedDiagnosticsWithOptionsFromIds(IEnumerable ids) + { + var builder = ImmutableArray.CreateBuilder<(DiagnosticDescriptor, ImmutableHashSet)>(); + foreach (var id in ids) + { + var descriptor = CreateDescriptorWithId( + id: id, + enforceOnBuild: EnforceOnBuild.Never, + hasAnyCodeStyleOption: false, + title: string.Empty); + + builder.Add((descriptor, [])); + } + + return builder.ToImmutableAndClear(); + } + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() + => throw new System.NotImplementedException(); + + protected override void InitializeWorker(AnalysisContext context) + { + } + } +} diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems index 1ce11ff7471ba..472b6d7133ff3 100644 --- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems +++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems @@ -9,6 +9,7 @@ Microsoft.CodeAnalysis.CSharp.Analyzers.UnitTests + @@ -181,4 +182,7 @@ + + + \ No newline at end of file diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs index 2a9338532b838..9b8b81878119e 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; @@ -81,8 +80,8 @@ protected AbstractBuiltInCodeStyleDiagnosticAnalyzer( /// /// Constructor for a code style analyzer with a multiple diagnostic descriptors with a code style editorconfig option that can be used to configure each descriptor. /// - protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableDictionary supportedDiagnosticsWithOptions) - : this(supportedDiagnosticsWithOptions.Keys.ToImmutableArray()) + protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, IOption2 Option)> supportedDiagnosticsWithOptions) + : this(supportedDiagnosticsWithOptions.SelectAsArray(static item => item.Descriptor)) { foreach (var (descriptor, option) in supportedDiagnosticsWithOptions) { @@ -94,8 +93,8 @@ protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableDictionary /// Constructor for a code style analyzer with multiple diagnostic descriptors with zero or more code style editorconfig options that can be used to configure each descriptor. /// - protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableDictionary> supportedDiagnosticsWithOptions) - : this(supportedDiagnosticsWithOptions.Keys.ToImmutableArray()) + protected AbstractBuiltInCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, ImmutableHashSet Options)> supportedDiagnosticsWithOptions) + : this(supportedDiagnosticsWithOptions.SelectAsArray(static item => item.Descriptor)) { foreach (var (descriptor, options) in supportedDiagnosticsWithOptions) { diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs index 59d80030b5808..b91fd230bf02f 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs @@ -2,14 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeStyle; diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs index 4ca8ddd49631f..922dc5ad9ff11 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer.cs @@ -100,19 +100,19 @@ protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableArray /// Constructor for a code style analyzer with a multiple diagnostic descriptors with a code style options that can be used to configure each descriptor. /// - protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableDictionary supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) + protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, IOption2 Option)> supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) : base(supportedDiagnosticsWithOptions) { - AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Keys, fadingOption); + AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Select(static item => item.Descriptor), fadingOption); } /// /// Constructor for a code style analyzer with multiple diagnostic descriptors with zero or more code style options that can be used to configure each descriptor. /// - protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableDictionary> supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) + protected AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(ImmutableArray<(DiagnosticDescriptor Descriptor, ImmutableHashSet Options)> supportedDiagnosticsWithOptions, PerLanguageOption2? fadingOption) : base(supportedDiagnosticsWithOptions) { - AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Keys, fadingOption); + AddDescriptorsToFadingOptionMapping(supportedDiagnosticsWithOptions.Select(static item => item.Descriptor), fadingOption); } private static void AddDiagnosticIdToFadingOptionMapping(string diagnosticId, PerLanguageOption2? fadingOption) diff --git a/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs index e356bc1c9b206..9f8bfae89f846 100644 --- a/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/FileHeaders/AbstractFileHeaderDiagnosticAnalyzer.cs @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using System.IO; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.FileHeaders; @@ -24,9 +22,11 @@ private static DiagnosticDescriptor CreateDescriptorForFileHeader(LocalizableStr => CreateDescriptorWithId(IDEDiagnosticIds.FileHeaderMismatch, EnforceOnBuildValues.FileHeaderMismatch, hasAnyCodeStyleOption: true, title, message); protected AbstractFileHeaderDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_invalidHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate) - .Add(s_missingHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate)) + : base( + [ + (s_invalidHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate), + (s_missingHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate) + ]) { } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs index 0240858e09729..42846f4bc11e2 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedParametersAndValues/AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs @@ -96,11 +96,13 @@ internal abstract partial class AbstractRemoveUnusedParametersAndValuesDiagnosti protected AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer( Option2> unusedValueExpressionStatementOption, Option2> unusedValueAssignmentOption) - : base(ImmutableDictionary.Empty - .Add(s_expressionValueIsUnusedRule, unusedValueExpressionStatementOption) - .Add(s_valueAssignedIsUnusedRule, unusedValueAssignmentOption) - .Add(s_unusedParameterRule, CodeStyleOptions2.UnusedParameters), - fadingOption: null) + : base( + [ + (s_expressionValueIsUnusedRule, unusedValueExpressionStatementOption), + (s_valueAssignedIsUnusedRule, unusedValueAssignmentOption), + (s_unusedParameterRule, CodeStyleOptions2.UnusedParameters) + ], + fadingOption: null) { } diff --git a/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs b/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs index a37aa66a8d07f..fdaf88e7069f3 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs @@ -4,19 +4,16 @@ // #define LOG -using System; using System.Collections.Concurrent; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -64,15 +61,17 @@ internal abstract class SimplifyTypeNamesDiagnosticAnalyzerBase>.Empty - .Add(s_descriptorSimplifyNames, []) - .Add(s_descriptorSimplifyMemberAccess, []) - .Add(s_descriptorPreferBuiltinOrFrameworkType, - [ - CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, - CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, - ]), - fadingOption: null) + : base( + [ + (s_descriptorSimplifyNames, []), + (s_descriptorSimplifyMemberAccess, []), + (s_descriptorPreferBuiltinOrFrameworkType, + [ + CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration, + CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, + ]) + ], + fadingOption: null) { } diff --git a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs index ea645fb31ee2a..a663612d196d2 100644 --- a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs @@ -8,7 +8,6 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.CodeStyle; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -82,9 +81,10 @@ public override DiagnosticAnalyzerCategory GetAnalyzerCategory() isUnnecessary: true); protected AbstractUseCollectionInitializerDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_descriptor, CodeStyleOptions2.PreferCollectionInitializer) - .Add(s_unnecessaryCodeDescriptor, CodeStyleOptions2.PreferCollectionInitializer)) + : base( + [ + (s_descriptor, CodeStyleOptions2.PreferCollectionInitializer) + ]) { } diff --git a/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs index 8e1c39d240270..aabbb18b1eb49 100644 --- a/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; @@ -62,9 +61,10 @@ internal abstract partial class AbstractUseObjectInitializerDiagnosticAnalyzer< protected abstract TAnalyzer GetAnalyzer(); protected AbstractUseObjectInitializerDiagnosticAnalyzer() - : base(ImmutableDictionary.Empty - .Add(s_descriptor, CodeStyleOptions2.PreferObjectInitializer) - .Add(s_unnecessaryCodeDescriptor, CodeStyleOptions2.PreferObjectInitializer)) + : base( + [ + (s_descriptor, CodeStyleOptions2.PreferObjectInitializer) + ]) { } From d3f3ff3ac5bb7a5a8f08240896da7dcfe7f4af4f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 14:00:31 -0700 Subject: [PATCH 243/292] Add more information in checksums --- .../Workspace/Solution/StateChecksums.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index dd1c5d79ec793..3ad15235208ad 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -486,6 +486,23 @@ public async Task FindAsync( await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } +<<<<<<< Updated upstream +======= + + public override string ToString() + => $""" + ProjectStateChecksums({ProjectId}) + Info={Info} + CompilationOptions={CompilationOptions} + ParseOptions={ParseOptions} + ProjectReferences={ProjectReferences.Checksum} + MetadataReferences={MetadataReferences.Checksum} + AnalyzerReferences={AnalyzerReferences.Checksum} + Documents={Documents.Checksum} + AdditionalDocuments={AdditionalDocuments.Checksum} + AnalyzerConfigDocuments={AnalyzerConfigDocuments.Checksum} + """; +>>>>>>> Stashed changes } internal sealed class DocumentStateChecksums( From bf0d0196e3d1e3127c778e7308224833ef83820b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 14:01:35 -0700 Subject: [PATCH 244/292] Apply suggestions from code review --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 3ad15235208ad..fd3cf01c230bc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -486,8 +486,6 @@ public async Task FindAsync( await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } -<<<<<<< Updated upstream -======= public override string ToString() => $""" @@ -502,7 +500,6 @@ public override string ToString() AdditionalDocuments={AdditionalDocuments.Checksum} AnalyzerConfigDocuments={AnalyzerConfigDocuments.Checksum} """; ->>>>>>> Stashed changes } internal sealed class DocumentStateChecksums( From 53e49f66d71b1f63195c7571b3f37ec6a17fbace Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 14:03:22 -0700 Subject: [PATCH 245/292] Fix --- .../Core/Portable/Workspace/Solution/StateChecksums.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index 3ad15235208ad..fd3cf01c230bc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -486,8 +486,6 @@ public async Task FindAsync( await ChecksumCollection.FindAsync(assetPath, state.AnalyzerConfigDocumentStates, searchingChecksumsLeft, onAssetFound, arg, cancellationToken).ConfigureAwait(false); } } -<<<<<<< Updated upstream -======= public override string ToString() => $""" @@ -502,7 +500,6 @@ public override string ToString() AdditionalDocuments={AdditionalDocuments.Checksum} AnalyzerConfigDocuments={AnalyzerConfigDocuments.Checksum} """; ->>>>>>> Stashed changes } internal sealed class DocumentStateChecksums( From 0c3133272c2e2c597114a6abec8037b7fd75db82 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 14:20:59 -0700 Subject: [PATCH 246/292] Cache diagnostics produced during workspace pull, to avoid unnecessary computations Docs --- ...stractWorkspaceDocumentDiagnosticSource.cs | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 4cc153fcc9739..53b3937749c0d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -3,10 +3,14 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; @@ -21,6 +25,13 @@ public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDia private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { + /// + /// Cached mapping between a project instance and all the diagnostics computed for it. This is used so that + /// once we compute the diagnostics once for a particular project, we don't need to recompute them again as we + /// walk every document within it. + /// + private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); + /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. /// @@ -40,14 +51,32 @@ public override async Task> GetDiagnosticsAsync( } else { - // We call GetDiagnosticsForIdsAsync as we want to ensure we get the full set of diagnostics for this document - // including those reported as a compilation end diagnostic. These are not included in document pull (uses GetDiagnosticsForSpan) due to cost. - // However we can include them as a part of workspace pull when FSA is on. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - Document.Project.Solution, Document.Project.Id, Document.Id, - diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, - includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); - return documentDiagnostics; + var projectDiagnostics = await GetProjectDiagnosticsAsync(diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + return projectDiagnostics.WhereAsArray(d => d.DocumentId == Document.Id); + } + } + + private async ValueTask> GetProjectDiagnosticsAsync( + IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + if (!s_projectToDiagnostics.TryGetValue(Document.Project, out var lazyDiagnostics)) + { + // Extracted into local to prevent captures. + lazyDiagnostics = GetLazyDiagnostics(); + } + + var result = await lazyDiagnostics.GetValueAsync(cancellationToken).ConfigureAwait(false); + return (ImmutableArray)result; + + AsyncLazy> GetLazyDiagnostics() + { + return s_projectToDiagnostics.GetValue( + Document.Project, + _ => AsyncLazy.Create>( + async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + Document.Project.Solution, Document.Project.Id, documentId: null, + diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, + includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false))); } } } From a9e72d6d1a1efb941a25c1c8375271c6f3efe46a Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 15:11:03 -0700 Subject: [PATCH 247/292] CR feedback --- .../DiagnosticSourceManager.cs | 57 ++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index 0482e5c8b9144..d255fd0fc6f97 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -48,8 +48,12 @@ public async ValueTask> CreateDiagnosticSource var providersDictionary = isDocument ? _documentProviders : _workspaceProviders; if (sourceName != null) { - Contract.ThrowIfFalse(providersDictionary.TryGetValue(sourceName, out var provider), $"Unrecognized source {sourceName}"); - return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + // VS does not distinguish between document and workspace sources. Thus it can request + // document diagnostics with workspace source name. We need to handle this case. + if (providersDictionary.TryGetValue(sourceName, out var provider)) + return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + + return []; } else { @@ -57,7 +61,7 @@ public async ValueTask> CreateDiagnosticSource using var _ = ArrayBuilder.GetInstance(out var sourcesBuilder); foreach (var kvp in providersDictionary) { - // Exclude task diagnostics from the aggregated sources. + // Exclude Task diagnostics from the aggregated sources. if (kvp.Key != PullDiagnosticCategories.Task) { var namedSources = await kvp.Value.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); @@ -66,32 +70,57 @@ public async ValueTask> CreateDiagnosticSource } var sources = sourcesBuilder.ToImmutableAndClear(); - if (!isDocument || sources.Length <= 1) + if (sources.Length <= 1) { return sources; } - else + + if (isDocument) { - // VS Code document handler (and legacy VS ?) expects a single source for document diagnostics. - // For more details see PublicDocumentPullDiagnosticsHandler.CreateReturn. + // Group all document sources into a single source. Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live"); - return [new AggregatedDocumentDiagnosticSource(sources)]; + sources = [new AggregatedDocumentDiagnosticSource(sources)]; + } + else + { + // For workspace we need to group sources by document and IsLiveSource + sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) + .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) + .ToImmutableArray(); } + + return sources; } } - private class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource + private class AggregatedDocumentDiagnosticSource : IDiagnosticSource { + private readonly ImmutableArray _sources; + + public static ImmutableArray AggregateIfNeeded(IEnumerable sources) + { + var result = sources.ToImmutableArray(); + if (result.Length > 1) + { + result = [new AggregatedDocumentDiagnosticSource(result)]; + } + + return result; + } + + public AggregatedDocumentDiagnosticSource(ImmutableArray sources) + => this._sources = sources; + public bool IsLiveSource() => true; - public Project GetProject() => sources[0].GetProject(); - public ProjectOrDocumentId GetId() => sources[0].GetId(); - public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier(); - public string ToDisplayString() => $"{this.GetType().Name}: count={sources.Length}"; + public Project GetProject() => _sources[0].GetProject(); + public ProjectOrDocumentId GetId() => _sources[0].GetId(); + public TextDocumentIdentifier? GetDocumentIdentifier() => _sources[0].GetDocumentIdentifier(); + public string ToDisplayString() => $"{this.GetType().Name}: count={_sources.Length}"; public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var diagnostics); - foreach (var source in sources) + foreach (var source in _sources) { var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); diagnostics.AddRange(namedDiagnostics); From 6dfc2597165104385b986a3cf5c3eb7a80522580 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 17:17:16 -0700 Subject: [PATCH 248/292] In progrss --- ...tionCompilationState.CompilationTracker.cs | 46 ++++++------------- ...eneratedFileReplacingCompilationTracker.cs | 12 ----- ...ionCompilationState.ICompilationTracker.cs | 1 - .../Solution/SolutionCompilationState.cs | 27 +++-------- .../CoreTest/SolutionTests/SolutionTests.cs | 3 +- 5 files changed, 21 insertions(+), 68 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index d38606df450db..11b1bd8884d08 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -529,18 +529,23 @@ await compilationState.GetCompilationAsync( if (creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.Create) { - // Client always wants an up to date metadata reference. Produce one for this project reference. - var metadataReference = await compilationState.GetMetadataReferenceAsync(projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false); + // Client always wants an up to date metadata reference. Produce one for this project + // reference. Because the policy is to always 'Create' here, we include cross language + // references, producing skeletons for them if necessary. + var metadataReference = await compilationState.GetMetadataReferenceAsync( + projectReference, this.ProjectState, includeCrossLanguage: true, cancellationToken).ConfigureAwait(false); AddMetadataReference(projectReference, metadataReference); } else { Contract.ThrowIfFalse(creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.CreateIfAbsent or SkeletonReferenceCreationPolicy.DoNotCreate); - // If not asked to explicit create an up to date skeleton, attempt to get a partial - // reference, or fallback to the last successful reference for this project if we can - // find one. - var metadataReference = compilationState.GetPartialMetadataReference(projectReference, this.ProjectState); + // Client does not want to force a skeleton reference to be created. Try to get a + // metadata reference cheaply in the case where this is a reference to the same + // language. If that fails, also attempt to get a reference to a skeleton assembly + // produced from one of our prior stale compilations. + var metadataReference = await compilationState.GetMetadataReferenceAsync( + projectReference, this.ProjectState, includeCrossLanguage: false, cancellationToken).ConfigureAwait(false); if (metadataReference is null) { var inProgressCompilationNotRef = staleCompilationWithGeneratedDocuments ?? compilationWithoutGeneratedDocuments; @@ -552,7 +557,8 @@ await compilationState.GetCompilationAsync( // create a real skeleton here. if (metadataReference is null && creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.CreateIfAbsent) { - metadataReference = await compilationState.GetMetadataReferenceAsync(projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false); + metadataReference = await compilationState.GetMetadataReferenceAsync( + projectReference, this.ProjectState, includeCrossLanguage: true, cancellationToken).ConfigureAwait(false); } AddMetadataReference(projectReference, metadataReference); @@ -647,32 +653,6 @@ private Compilation CreateEmptyCompilation() } } - /// - /// Attempts to get (without waiting) a metadata reference to a possibly in progress - /// compilation. Only actual compilation references are returned. Could potentially - /// return null if nothing can be provided. - /// - public MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) - { - if (ProjectState.LanguageServices == fromProject.LanguageServices) - { - // if we have a compilation and its the correct language, use a simple compilation reference in any - // state it happens to be in right now - if (ReadState() is CompilationTrackerState compilationState) - return compilationState.CompilationWithoutGeneratedDocuments.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); - } - else - { - // Cross project reference. We need a skeleton reference. Skeletons are too expensive to - // generate on demand. So just try to see if we can grab the last generated skeleton for that - // project. - var properties = new MetadataReferenceProperties(aliases: projectReference.Aliases, embedInteropTypes: projectReference.EmbedInteropTypes); - return _skeletonReferenceCache.TryGetAlreadyBuiltMetadataReference(properties); - } - - return null; - } - public Task HasSuccessfullyLoadedAsync( SolutionCompilationState compilationState, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index 990d4467db2a7..83dd3fa0a8ce6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs @@ -147,18 +147,6 @@ private async Task ComputeDependentChecksumAsync(SolutionCompilationSt await UnderlyingTracker.GetDependentChecksumAsync(compilationState, cancellationToken).ConfigureAwait(false), (await _replacementDocumentStates.GetDocumentChecksumsAndIdsAsync(cancellationToken).ConfigureAwait(false)).Checksum); - public MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) - { - // This method is used if you're forking a solution with partial semantics, and used to quickly produce references. - // So this method should only be called if: - // - // 1. Project A has a open source generated document, and this CompilationTracker represents A - // 2. Project B references that A, and is being frozen for partial semantics. - // - // We generally don't use partial semantics in a different project than the open file, so this isn't a scenario we need to support. - throw new NotImplementedException(); - } - public async ValueTask> GetSourceGeneratedDocumentStatesAsync( SolutionCompilationState compilationState, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs index c910b9301214d..30455ec7cfd49 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs @@ -49,7 +49,6 @@ private interface ICompilationTracker Task GetDependentSemanticVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); Task GetDependentChecksumAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); - MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference); ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); ValueTask> GetSourceGeneratorDiagnosticsAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); ValueTask GetSourceGeneratorRunResultAsync(SolutionCompilationState solution, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 960c7f165945d..058055eb643fe 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -1009,24 +1009,6 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( return GetCompilationTracker(documentId.ProjectId).TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); } - /// - /// Attempt to get the best readily available compilation for the project. It may be a - /// partially built compilation. - /// - private MetadataReference? GetPartialMetadataReference( - ProjectReference projectReference, - ProjectState fromProject) - { - // Try to get the compilation state for this project. If it doesn't exist, don't do any - // more work. - if (!_projectIdToTrackerMap.TryGetValue(projectReference.ProjectId, out var state)) - { - return null; - } - - return state.GetPartialMetadataReference(fromProject, projectReference); - } - /// /// Get a metadata reference to this compilation info's compilation with respect to /// another project. For cross language references produce a skeletal assembly. If the @@ -1034,7 +1016,7 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( /// needed and does not exist, it is also built. /// private async Task GetMetadataReferenceAsync( - ICompilationTracker tracker, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) + ICompilationTracker tracker, ProjectState fromProject, ProjectReference projectReference, bool includeCrossLanguage, CancellationToken cancellationToken) { try { @@ -1046,6 +1028,9 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); } + if (!includeCrossLanguage) + return null; + // otherwise get a metadata only image reference that is built by emitting the metadata from the // referenced project's compilation and re-importing it. using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_GetMetadataOnlyImage, cancellationToken)) @@ -1065,14 +1050,14 @@ public ValueTask> GetSourceGeneratorDiagnosticsAsync( /// can happen when trying to build a skeleton reference that fails to build. /// public Task GetMetadataReferenceAsync( - ProjectReference projectReference, ProjectState fromProject, CancellationToken cancellationToken) + ProjectReference projectReference, ProjectState fromProject, bool includeCrossLanguage, CancellationToken cancellationToken) { try { // Get the compilation state for this project. If it's not already created, then this // will create it. Then force that state to completion and get a metadata reference to it. var tracker = this.GetCompilationTracker(projectReference.ProjectId); - return GetMetadataReferenceAsync(tracker, fromProject, projectReference, cancellationToken); + return GetMetadataReferenceAsync(tracker, fromProject, projectReference, includeCrossLanguage, cancellationToken); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) { diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index e1ac87c0b8243..67e9cabedec10 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -2216,7 +2216,8 @@ private static async Task ValidateSolutionAndCompilationsAsync(Solution solution { if (solution.ContainsProject(referenced.ProjectId)) { - var referencedMetadata = await solution.CompilationState.GetMetadataReferenceAsync(referenced, solution.GetProjectState(project.Id), CancellationToken.None); + var referencedMetadata = await solution.CompilationState.GetMetadataReferenceAsync( + referenced, solution.GetProjectState(project.Id), includeCrossLanguage: true, CancellationToken.None); Assert.NotNull(referencedMetadata); if (referencedMetadata is CompilationReference compilationReference) { From 3b3fbd7f1b64ea88a5be4953c648e640fc202385 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 23 Apr 2024 16:37:50 -0700 Subject: [PATCH 249/292] Update to support additional documents --- .../Test/CodeFixes/CodeFixServiceTests.cs | 2 +- .../DiagnosticAnalyzerServiceTests.cs | 6 +----- .../MockDiagnosticAnalyzerService.cs | 2 +- .../Diagnostics/IDiagnosticAnalyzerService.cs | 17 ++++++++++++++--- .../Diagnostics/DiagnosticAnalyzerService.cs | 4 ++-- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 19 ++++++++++++------- ...stractWorkspaceDocumentDiagnosticSource.cs | 6 +++++- .../ExternalDiagnosticUpdateSourceTests.vb | 2 +- 8 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 1bf99ecb4ba35..0594aa086c502 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -1080,7 +1080,7 @@ void M() : root.DescendantNodes().OfType().First().Span; await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( - sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, + sourceDocument.Project.Solution, sourceDocument.Project.Id, sourceDocument.Id, diagnosticIds: null, shouldIncludeAnalyzer: null, getDocuments: null, includeSuppressedDiagnostics: true, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index e734ac991f1ab..2c7e97f9e7084 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -12,20 +12,16 @@ using Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics.CSharp; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; using Microsoft.CodeAnalysis.Editor.Test; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote.Diagnostics; using Microsoft.CodeAnalysis.Remote.Testing; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.UnitTests; using Roslyn.Test.Utilities; using Roslyn.Test.Utilities.TestGenerators; using Roslyn.Utilities; @@ -72,7 +68,7 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var globalOptions = exportProvider.GetExportedValue(); var diagnostics = await analyzer.GetDiagnosticsForIdsAsync( - workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, + workspace.CurrentSolution, projectId: null, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, getDocuments: null, includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, CancellationToken.None); Assert.NotEmpty(diagnostics); } diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs index 6bd490efea2e5..424a6876218c1 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs @@ -61,7 +61,7 @@ public Task> GetCachedDiagnosticsAsync(Workspace public Task> GetDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => throw new NotImplementedException(); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task> GetDiagnosticsForSpanAsync(TextDocument document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeCompilerDiagnostics, bool includeSuppressedDiagnostics, ICodeActionRequestPriorityProvider priorityProvider, Func? addOperationScope, DiagnosticKind diagnosticKind, bool isExplicit, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index f2099d71bf024..6ac5f90bcae97 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -75,11 +76,13 @@ internal interface IDiagnosticAnalyzerService /// complete set of non-local document diagnostics. /// /// Cancellation token. - Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); + Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocumentIds, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken); /// - /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from the given solution. all diagnostics returned should be up-to-date with respect to the given solution. - /// Note that this method doesn't return any document diagnostics. Use to also fetch those. + /// Get project diagnostics (diagnostics with no source location) of the given diagnostic ids and/or analyzers from + /// the given solution. all diagnostics returned should be up-to-date with respect to the given solution. Note that + /// this method doesn't return any document diagnostics. Use to also fetch + /// those. /// /// Solution to fetch the diagnostics for. /// Optional project to scope the returned diagnostics. @@ -181,4 +184,12 @@ public static Task> GetDiagnosticsForSpanAsync(th includeCompilerDiagnostics: true, includeSuppressedDiagnostics, priorityProvider, addOperationScope, diagnosticKind, isExplicit, cancellationToken); } + + public static Task> GetDiagnosticsForIdsAsync( + this IDiagnosticAnalyzerService service, Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + { + return service.GetDiagnosticsForIdsAsync( + solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocumentIds: null, + includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs index d2d7ff6d55715..be32623daf749 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs @@ -128,10 +128,10 @@ public async Task ForceAnalyzeProjectAsync(Project project, CancellationToken ca } public Task> GetDiagnosticsForIdsAsync( - Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { var analyzer = CreateIncrementalAnalyzer(solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + return analyzer.GetDiagnosticsForIdsAsync(solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 4341ec3017262..f24820a50f801 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -18,11 +18,11 @@ internal partial class DiagnosticIncrementalAnalyzer public Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) => new IdeCachedDiagnosticGetter(this, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); - public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); + public Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId, diagnosticIds, shouldIncludeAnalyzer, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics).GetDiagnosticsAsync(cancellationToken); public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeSuppressedDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) - => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); + => new IdeLatestDiagnosticGetter(this, solution, projectId, documentId: null, diagnosticIds, shouldIncludeAnalyzer, getDocuments: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics).GetProjectDiagnosticsAsync(cancellationToken); private abstract class DiagnosticGetter { @@ -35,6 +35,8 @@ private abstract class DiagnosticGetter protected readonly bool IncludeLocalDocumentDiagnostics; protected readonly bool IncludeNonLocalDocumentDiagnostics; + private readonly Func> _getDocuments; + private ImmutableArray.Builder? _lazyDataBuilder; public DiagnosticGetter( @@ -42,6 +44,7 @@ public DiagnosticGetter( Solution solution, ProjectId? projectId, DocumentId? documentId, + Func>? getDocuments, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) @@ -50,6 +53,7 @@ public DiagnosticGetter( Solution = solution; DocumentId = documentId; + _getDocuments = getDocuments ?? (static (project, documentId) => documentId != null ? [documentId] : project.DocumentIds); ProjectId = projectId ?? documentId?.ProjectId; IncludeSuppressedDiagnostics = includeSuppressedDiagnostics; @@ -76,7 +80,7 @@ public async Task> GetDiagnosticsAsync(Cancellati return GetDiagnosticData(); } - var documentIds = DocumentId != null ? [DocumentId] : project.DocumentIds; + var documentIds = _getDocuments(project, DocumentId); // return diagnostics specific to one project or document var includeProjectNonLocalResult = DocumentId == null; @@ -129,7 +133,7 @@ private bool ShouldIncludeSuppressedDiagnostic(DiagnosticData diagnostic) private sealed class IdeCachedDiagnosticGetter : DiagnosticGetter { public IdeCachedDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId? projectId, DocumentId? documentId, bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) - : base(owner, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) + : base(owner, solution, projectId, documentId, getDocuments: null, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) { } @@ -229,8 +233,9 @@ private sealed class IdeLatestDiagnosticGetter : DiagnosticGetter public IdeLatestDiagnosticGetter( DiagnosticIncrementalAnalyzer owner, Solution solution, ProjectId? projectId, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, - bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) - : base(owner, solution, projectId, documentId, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) + Func>? getDocuments, + bool includeSuppressedDiagnostics, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics) + : base(owner, solution, projectId, documentId, getDocuments, includeSuppressedDiagnostics, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics) { _diagnosticIds = diagnosticIds; _shouldIncludeAnalyzer = shouldIncludeAnalyzer; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 53b3937749c0d..cc301aa60d8b3 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -75,7 +76,10 @@ AsyncLazy> GetLazyDiagnostics() _ => AsyncLazy.Create>( async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( Document.Project.Solution, Document.Project.Id, documentId: null, - diagnosticIds: null, shouldIncludeAnalyzer, includeSuppressedDiagnostics: false, + diagnosticIds: null, shouldIncludeAnalyzer, + // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. + static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], + includeSuppressedDiagnostics: false, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false))); } } diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 7b9b26917d379..edbf03f93fae6 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -430,7 +430,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function - Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync + Public Function GetDiagnosticsForIdsAsync(solution As Solution, projectId As ProjectId, documentId As DocumentId, diagnosticIds As ImmutableHashSet(Of String), shouldIncludeAnalyzer As Func(Of DiagnosticAnalyzer, Boolean), getDocuments As Func(Of Project, DocumentId, IReadOnlyList(Of DocumentId)), includeSuppressedDiagnostics As Boolean, includeLocalDocumentDiagnostics As Boolean, includeNonLocalDocumentDiagnostics As Boolean, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of DiagnosticData)) Implements IDiagnosticAnalyzerService.GetDiagnosticsForIdsAsync Return SpecializedTasks.EmptyImmutableArray(Of DiagnosticData)() End Function From e535a26a8a32b593b73d24ae46bb2e05e864041b Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Tue, 23 Apr 2024 17:42:45 -0700 Subject: [PATCH 250/292] CR feedback --- .../Options/SolutionCrawlerOptionsStorage.cs | 1 - ...bstractDocumentDiagnosticSourceProvider.cs | 24 ----- .../AbstractDocumentPullDiagnosticHandler.cs | 29 +++++- ...stractWorkspaceDiagnosticSourceProvider.cs | 8 -- ...AbstractWorkspacePullDiagnosticsHandler.cs | 8 ++ .../DiagnosticSourceManager.cs | 20 ++-- ...ntaxAndSemanticDiagnosticSourceProvider.cs | 4 +- .../DocumentTaskDiagnosticSourceProvider.cs | 5 +- ...ocumentNonLocalDiagnosticSourceProvider.cs | 11 +-- ...cePullDiagnosticsHandler_IOnInitialized.cs | 18 +--- ...mentsAndProjectDiagnosticSourceProvider.cs | 95 +++++++++---------- ...EditAndContinueDiagnosticSourceProvider.cs | 9 +- .../WorkspaceTaskDiagnosticSourceProvider.cs | 25 +++-- 13 files changed, 114 insertions(+), 143 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs index 8aacbe48a42fe..cc7fc24bc3875 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs index f5f014ddbde07..74984ee495ce4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs @@ -14,28 +14,4 @@ internal abstract class AbstractDocumentDiagnosticSourceProvider(string name) : public string Name => name; public abstract ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); - - protected static TextDocument? GetOpenDocument(RequestContext context) - { - // Note: context.Document may be null in the case where the client is asking about a document that we have - // since removed from the workspace. In this case, we don't really have anything to process. - // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. - // - // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each - // handler treats those as separate worlds that they are responsible for. - var textDocument = context.TextDocument; - if (textDocument is null) - { - context.TraceInformation("Ignoring diagnostics request because no text document was provided"); - return null; - } - - if (!context.IsTracking(textDocument.GetURI())) - { - context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); - return null; - } - - return textDocument; - } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index 648dd3e0dc428..95792e82c09c4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -26,10 +26,37 @@ internal abstract class AbstractDocumentPullDiagnosticHandler> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { + if (GetOpenDocument(context) is null) + return new([]); + return DiagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, isDocument: true, cancellationToken); } - public abstract TextDocumentIdentifier? GetTextDocumentIdentifier(TDiagnosticsParams diagnosticsParams); + protected static TextDocument? GetOpenDocument(RequestContext context) + { + // Note: context.Document may be null in the case where the client is asking about a document that we have + // since removed from the workspace. In this case, we don't really have anything to process. + // GetPreviousResults will be used to properly realize this and notify the client that the doc is gone. + // + // Only consider open documents here (and only closed ones in the WorkspacePullDiagnosticHandler). Each + // handler treats those as separate worlds that they are responsible for. + var textDocument = context.TextDocument; + if (textDocument is null) + { + context.TraceInformation("Ignoring diagnostics request because no text document was provided"); + return null; + } + + if (!context.IsTracking(textDocument.GetURI())) + { + context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); + return null; + } + + return textDocument; + } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs index 0316bd9b08909..caef56d8d2669 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspaceDiagnosticSourceProvider.cs @@ -19,14 +19,6 @@ internal abstract class AbstractWorkspaceDiagnosticSourceProvider(string name) : public abstract ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); - protected static bool ShouldIgnoreContext(RequestContext context) - { - // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace - // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for - // document-diagnostics instead. - return context.ServerKind == WellKnownLspServerKinds.RazorLspServer; - } - protected static IEnumerable GetProjectsInPriorityOrder( Solution solution, ImmutableArray supportedLanguages) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index 6b6fee7538432..72ee8b0268d84 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -53,6 +53,14 @@ public void Dispose() protected override ValueTask> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) { + if (context.ServerKind == WellKnownLspServerKinds.RazorLspServer) + { + // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace + // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for + // document-diagnostics instead. + return new([]); + } + return _diagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, false, cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index d255fd0fc6f97..dffea4db7b4ae 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -53,6 +53,7 @@ public async ValueTask> CreateDiagnosticSource if (providersDictionary.TryGetValue(sourceName, out var provider)) return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); + context.TraceInformation($"No `{sourceName}` diagnostics for {isDocument}"); return []; } else @@ -83,7 +84,7 @@ public async ValueTask> CreateDiagnosticSource } else { - // For workspace we need to group sources by document and IsLiveSource + // For workspace we need to group sources by source id and IsLiveSource sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) .ToImmutableArray(); @@ -93,10 +94,8 @@ public async ValueTask> CreateDiagnosticSource } } - private class AggregatedDocumentDiagnosticSource : IDiagnosticSource + private class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource { - private readonly ImmutableArray _sources; - public static ImmutableArray AggregateIfNeeded(IEnumerable sources) { var result = sources.ToImmutableArray(); @@ -108,19 +107,16 @@ public static ImmutableArray AggregateIfNeeded(IEnumerable sources) - => this._sources = sources; - public bool IsLiveSource() => true; - public Project GetProject() => _sources[0].GetProject(); - public ProjectOrDocumentId GetId() => _sources[0].GetId(); - public TextDocumentIdentifier? GetDocumentIdentifier() => _sources[0].GetDocumentIdentifier(); - public string ToDisplayString() => $"{this.GetType().Name}: count={_sources.Length}"; + public Project GetProject() => sources[0].GetProject(); + public ProjectOrDocumentId GetId() => sources[0].GetId(); + public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier(); + public string ToDisplayString() => $"{this.GetType().Name}: count={sources.Length}"; public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var diagnostics); - foreach (var source in _sources) + foreach (var source in sources) { var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); diagnostics.AddRange(namedDiagnostics); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs index 436465a8f351d..5f83473c67c8e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -26,10 +26,10 @@ public AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(IDiagnosticAnal public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (GetOpenDocument(context) is not TextDocument textDocument) + if (context.TextDocument is null) return new([]); - var source = new DocumentDiagnosticSource(_diagnosticAnalyzerService, _kind, textDocument); + var source = new DocumentDiagnosticSource(_diagnosticAnalyzerService, _kind, context.TextDocument); return new([source]); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs index c020ee7e30e81..d721d1674e65d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs @@ -20,10 +20,7 @@ internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptio { public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (GetOpenDocument(context) is not TextDocument textDocument) - return new([]); - - if (textDocument is not Document document) + if (context.TextDocument is not Document document) { context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); return new([]); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs index 876f058999c8d..b8121d1aa09d5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -26,15 +26,14 @@ internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - var textDocument = GetOpenDocument(context); - if (textDocument is null) - return new([]); - // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + if (context.TextDocument == null || + globalOptions.GetBackgroundAnalysisScope(context.TextDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + { return new([]); + } - return new([new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]); + return new([new NonLocalDocumentDiagnosticSource(context.TextDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]); // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index a87be284174f0..5225529cb2d9f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public @@ -14,7 +13,7 @@ internal sealed partial class PublicWorkspacePullDiagnosticsHandler : IOnInitial { public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) { - if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true && IsFsaEnabled()) + if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true) { var sources = _diagnosticSourceManager.GetSourceNames(isDocument: false); var regParams = new RegistrationParams @@ -30,23 +29,14 @@ Registration FromSourceName(string sourceName) { return new() { + // Due to https://github.com/microsoft/language-server-protocol/issues/1723 + // we need to use textDocument/diagnostic instead of workspace/diagnostic + Method = Methods.TextDocumentDiagnosticName, Id = sourceName, - Method = Methods.WorkspaceDiagnosticName, RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName, InterFileDependencies = true, WorkDoneProgress = true } }; } } - - bool IsFsaEnabled() - { - foreach (var language in context.SupportedLanguages) - { - if (GlobalOptions.GetBackgroundAnalysisScope(language) == BackgroundAnalysisScope.FullSolution) - return true; - } - - return false; - } } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs index 38e94f79aceaa..2a13c533b9094 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -45,73 +45,68 @@ internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( /// public override async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (!ShouldIgnoreContext(context)) - { - Contract.ThrowIfNull(context.Solution); + Contract.ThrowIfNull(context.Solution); - using var _ = ArrayBuilder.GetInstance(out var result); + using var _ = ArrayBuilder.GetInstance(out var result); - var solution = context.Solution; - var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; - var codeAnalysisService = solution.Services.GetRequiredService(); + var solution = context.Solution; + var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; + var codeAnalysisService = solution.Services.GetRequiredService(); - foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) - await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) + await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); - return result.ToImmutableAndClear(); + return result.ToImmutableAndClear(); - async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) - { - var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); - if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) - return; + async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + { + var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); + if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) + return; - Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled - ? ShouldIncludeAnalyzer : null; + Func? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled + ? ShouldIncludeAnalyzer : null; - AddDocumentSources(project.Documents); - AddDocumentSources(project.AdditionalDocuments); + AddDocumentSources(project.Documents); + AddDocumentSources(project.AdditionalDocuments); - // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. - if (enableDiagnosticsInSourceGeneratedFiles) - { - var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); - AddDocumentSources(sourceGeneratedDocuments); - } + // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. + if (enableDiagnosticsInSourceGeneratedFiles) + { + var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + AddDocumentSources(sourceGeneratedDocuments); + } - // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. - AddProjectSource(); + // Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document. + AddProjectSource(); - return; + return; - void AddDocumentSources(IEnumerable documents) + void AddDocumentSources(IEnumerable documents) + { + foreach (var document in documents) { - foreach (var document in documents) + if (!ShouldSkipDocument(context, document)) { - if (!ShouldSkipDocument(context, document)) - { - // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. - var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) - : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); - result.Add(documentDiagnosticSource); - } + // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. + var documentDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); + result.Add(documentDiagnosticSource); } } + } - void AddProjectSource() - { - var projectDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) - : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); - result.Add(projectDiagnosticSource); - } - - bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) - => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + void AddProjectSource() + { + var projectDiagnosticSource = fullSolutionAnalysisEnabled + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) + : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); + result.Add(projectDiagnosticSource); } - } - return []; + bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) + => analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled; + } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs index cb8ff971b769b..e47e52bc8bc60 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceEditAndContinueDiagnosticSourceProvider.cs @@ -20,12 +20,7 @@ internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : Abstr { public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (!ShouldIgnoreContext(context)) - { - Contract.ThrowIfNull(context.Solution); - return EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken); - } - - return new([]); + Contract.ThrowIfNull(context.Solution); + return EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs index 32b09034eebe2..33755784ebd49 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspaceTaskDiagnosticSourceProvider.cs @@ -23,25 +23,22 @@ internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOpti { public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - if (!ShouldIgnoreContext(context)) - { - Contract.ThrowIfNull(context.Solution); + Contract.ThrowIfNull(context.Solution); - // Only compute task list items for closed files if the option is on for it. - if (globalOptions.GetTaskListOptions().ComputeForClosedFiles) + // Only compute task list items for closed files if the option is on for it. + if (globalOptions.GetTaskListOptions().ComputeForClosedFiles) + { + using var _ = ArrayBuilder.GetInstance(out var result); + foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) { - using var _ = ArrayBuilder.GetInstance(out var result); - foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) + foreach (var document in project.Documents) { - foreach (var document in project.Documents) - { - if (!ShouldSkipDocument(context, document)) - result.Add(new TaskListDiagnosticSource(document, globalOptions)); - } + if (!ShouldSkipDocument(context, document)) + result.Add(new TaskListDiagnosticSource(document, globalOptions)); } - - return new(result.ToImmutableAndClear()); } + + return new(result.ToImmutableAndClear()); } return new([]); From 9030db36c6d8353248b96b97a234d7ec22c6d11e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 09:04:21 -0700 Subject: [PATCH 251/292] Avoid unnecessary boxing --- .../AbstractWorkspaceDocumentDiagnosticSource.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index cc301aa60d8b3..4bff28c7035c8 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; @@ -31,7 +29,7 @@ private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, /// once we compute the diagnostics once for a particular project, we don't need to recompute them again as we /// walk every document within it. /// - private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); + private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. @@ -67,13 +65,13 @@ private async ValueTask> GetProjectDiagnosticsAsy } var result = await lazyDiagnostics.GetValueAsync(cancellationToken).ConfigureAwait(false); - return (ImmutableArray)result; + return result; - AsyncLazy> GetLazyDiagnostics() + AsyncLazy> GetLazyDiagnostics() { return s_projectToDiagnostics.GetValue( Document.Project, - _ => AsyncLazy.Create>( + _ => AsyncLazy.Create( async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( Document.Project.Solution, Document.Project.Id, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer, From 540104364d182a5e4e8b4373cb07f5af9f7657a9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 09:29:54 -0700 Subject: [PATCH 252/292] Also switch to a lookup --- ...stractWorkspaceDocumentDiagnosticSource.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 4bff28c7035c8..c3b1a48eac2e0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -29,7 +29,7 @@ private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, /// once we compute the diagnostics once for a particular project, we don't need to recompute them again as we /// walk every document within it. /// - private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); + private static readonly ConditionalWeakTable>> s_projectToDiagnostics = new(); /// /// This is a normal document source that represents live/fresh diagnostics that should supersede everything else. @@ -65,20 +65,24 @@ private async ValueTask> GetProjectDiagnosticsAsy } var result = await lazyDiagnostics.GetValueAsync(cancellationToken).ConfigureAwait(false); - return result; + return result[Document.Id].ToImmutableArray(); - AsyncLazy> GetLazyDiagnostics() + AsyncLazy> GetLazyDiagnostics() { return s_projectToDiagnostics.GetValue( Document.Project, _ => AsyncLazy.Create( - async cancellationToken => await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( - Document.Project.Solution, Document.Project.Id, documentId: null, - diagnosticIds: null, shouldIncludeAnalyzer, - // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. - static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], - includeSuppressedDiagnostics: false, - includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false))); + async cancellationToken => + { + var allDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + Document.Project.Solution, Document.Project.Id, documentId: null, + diagnosticIds: null, shouldIncludeAnalyzer, + // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this project. + static (project, _) => [.. project.DocumentIds.Concat(project.AdditionalDocumentIds)], + includeSuppressedDiagnostics: false, + includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); + return allDiagnostics.Where(d => d.DocumentId != null).ToLookup(d => d.DocumentId!); + })); } } } From 712fa16e06aca9a73e50c1c2bc486ee099cacbde Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Wed, 24 Apr 2024 09:30:39 -0700 Subject: [PATCH 253/292] Make hot reload diagnostics more pass thru --- .../DiagnosticSourceManager.cs | 41 ++++++++------ .../Contracts/HotReloadDocumentDiagnostics.cs | 14 ----- .../Contracts/HotReloadRequestContext.cs | 17 ++++++ .../Contracts/IHotReloadDiagnosticManager.cs | 40 ++++++------- .../Contracts/IHotReloadDiagnosticSource.cs | 25 ++++----- .../IHotReloadDiagnosticSourceProvider.cs | 27 +++++++++ ...stractHotReloadDiagnosticSourceProvider.cs | 21 ------- ...cumentHotReloadDiagnosticSourceProvider.cs | 41 -------------- .../Internal/HotReloadDiagnosticManager.cs | 29 ++++++---- .../Internal/HotReloadDiagnosticSource.cs | 13 +++-- .../HotReloadDiagnosticSourceProvider.cs | 56 +++++++++++++++++++ ...kspaceHotReloadDiagnosticSourceProvider.cs | 51 ----------------- .../InternalAPI.Unshipped.txt | 45 ++++++++------- 13 files changed, 205 insertions(+), 215 deletions(-) delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs create mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs delete mode 100644 src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs index dffea4db7b4ae..f213c0b3e0495 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DiagnosticSourceManager.cs @@ -71,27 +71,32 @@ public async ValueTask> CreateDiagnosticSource } var sources = sourcesBuilder.ToImmutableAndClear(); - if (sources.Length <= 1) - { - return sources; - } - - if (isDocument) - { - // Group all document sources into a single source. - Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live"); - sources = [new AggregatedDocumentDiagnosticSource(sources)]; - } - else - { - // For workspace we need to group sources by source id and IsLiveSource - sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) - .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) - .ToImmutableArray(); - } + return AggregateSourcesIfNeeded(sources, isDocument); + } + } + public static ImmutableArray AggregateSourcesIfNeeded(ImmutableArray sources, bool isDocument) + { + if (sources.Length <= 1) + { return sources; } + + if (isDocument) + { + // Group all document sources into a single source. + Debug.Assert(sources.All(s => s.IsLiveSource()), "All document sources should be live"); + sources = [new AggregatedDocumentDiagnosticSource(sources)]; + } + else + { + // For workspace we need to group sources by source id and IsLiveSource + sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) + .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) + .ToImmutableArray(); + } + + return sources; } private class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs deleted file mode 100644 index 13e899beb1c4c..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadDocumentDiagnostics.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts -{ - internal class HotReloadDocumentDiagnostics(DocumentId documentId, ImmutableArray diagnostics) - { - public DocumentId DocumentId => documentId; - public ImmutableArray Diagnostics => diagnostics; - } -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs new file mode 100644 index 0000000000000..6e879f2535158 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using LSP = Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +internal class HotReloadRequestContext(RequestContext context) +{ + internal LSP.ClientCapabilities ClientCapabilities => context.GetRequiredClientCapabilities(); + public TextDocument? TextDocument => context.TextDocument; + public Solution? Solution => context.Solution; + public bool IsTracking(TextDocument textDocument) => context.IsTracking(textDocument.GetURI()); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs index 29d9562916cdc..f1fe7a600dae4 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticManager.cs @@ -2,30 +2,30 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Collections.Immutable; -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +internal interface IHotReloadDiagnosticManager { - internal interface IHotReloadDiagnosticManager - { - /// - /// Hot reload diagnostics for all sources. - /// - ImmutableArray Sources { get; } + /// + /// Refreshes hot reload diagnostics. + /// + void RequestRefresh(); - /// - /// Registers source of hot reload diagnostics. Callers are responsible for refreshing diagnostics after registration. - /// - void Register(IHotReloadDiagnosticSource source); + /// + /// Registers providers of hot reload diagnostics. Callers are responsible for refreshing diagnostics after registration. + /// + void Register(IEnumerable providers); - /// - /// Unregisters source of hot reload diagnostics. Callers are responsible for refreshing diagnostics after un-registration. - /// - void Unregister(IHotReloadDiagnosticSource source); + /// + /// Unregisters providers of hot reload diagnostics. Callers are responsible for refreshing diagnostics after un-registration. + /// + void Unregister(IEnumerable providers); - /// - /// Requests refresh of hot reload diagnostics. - /// - void Refresh(); - } + /// + /// Providers. + /// + ImmutableArray Providers { get; } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs index 661ecc1f05724..05f1cee575b41 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs @@ -6,21 +6,20 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +/// +/// Source of hot reload diagnostics. +/// +internal interface IHotReloadDiagnosticSource { /// - /// Source for hot reload diagnostics. + /// Text document for which diagnostics are provided. /// - internal interface IHotReloadDiagnosticSource - { - /// - /// Provides list of document ids that have hot reload diagnostics. - /// - ValueTask> GetDocumentIdsAsync(CancellationToken cancellationToken); + TextDocument TextDocument { get; } - /// - /// Provides list of diagnostics for the given document. - /// - ValueTask> GetDocumentDiagnosticsAsync(TextDocument document, CancellationToken cancellationToken); - } + /// + /// Provides list of diagnostics for the given document. + /// + ValueTask> GetDiagnosticsAsync(HotReloadRequestContext request, CancellationToken cancellationToken); } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..586a6a2d8ec90 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; + +/// +/// Provides diagnostic sources. +/// +internal interface IHotReloadDiagnosticSourceProvider +{ + /// + /// True if this provider is for documents. False if it is for a workspace, i.e. for unopened documents. + /// + bool IsDocument { get; } + + /// + /// Creates the diagnostic sources. + /// + /// The context. + /// The cancellation token. + ValueTask> CreateDiagnosticSourcesAsync(HotReloadRequestContext context, CancellationToken cancellationToken); +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs deleted file mode 100644 index 832c1c6aa7c97..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/AbstractHotReloadDiagnosticSourceProvider.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -internal abstract class AbstractHotReloadDiagnosticSourceProvider : IDiagnosticSourceProvider -{ - string IDiagnosticSourceProvider.Name => "HotReloadDiagnostics"; - bool IDiagnosticSourceProvider.IsDocument => throw new NotImplementedException(); - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - => throw new NotImplementedException(); - -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs deleted file mode 100644 index f568fffbc8a43..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/DocumentHotReloadDiagnosticSourceProvider.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -[Export(typeof(IDiagnosticSourceProvider)), Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class DocumentHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadDiagnosticManager) - : AbstractHotReloadDiagnosticSourceProvider - , IDiagnosticSourceProvider -{ - bool IDiagnosticSourceProvider.IsDocument => true; - - ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - if (context.GetTrackedDocument() is { } textDocument) - { - List sources = new(); - foreach (var hotReloadSource in hotReloadDiagnosticManager.Sources) - { - sources.Add(new HotReloadDiagnosticSource(textDocument, hotReloadSource)); - } - - return new(sources.ToImmutableArray()); - } - - return new([]); - } -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs index b045e419d3eb7..8ce6103c47a01 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; @@ -14,20 +15,26 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; [Export(typeof(IHotReloadDiagnosticManager)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class HotReloadDiagnosticManager(IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager +internal sealed class HotReloadDiagnosticManager([Import] IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager { - private ImmutableArray _sources = ImmutableArray.Empty; + private ImmutableArray _providers = ImmutableArray.Empty; + ImmutableArray IHotReloadDiagnosticManager.Providers => _providers; + void IHotReloadDiagnosticManager.RequestRefresh() => diagnosticsRefresher.RequestWorkspaceRefresh(); - ImmutableArray IHotReloadDiagnosticManager.Sources => _sources; - void IHotReloadDiagnosticManager.Refresh() => diagnosticsRefresher.RequestWorkspaceRefresh(); - - void IHotReloadDiagnosticManager.Register(IHotReloadDiagnosticSource source) + void IHotReloadDiagnosticManager.Register(IEnumerable providers) { - // We use array instead of e.g. HashSet because we expect the number of sources to be small. Usually 1. - if (!_sources.Contains(source)) - _sources = _sources.Add(source); + // We use array instead of e.g. HashSet because we expect the number of sources to be small. + // Usually 2, one workspace and one document provider. + foreach (var provider in providers) + { + if (!_providers.Contains(provider)) + _providers = _providers.Add(provider); + } } - void IHotReloadDiagnosticManager.Unregister(IHotReloadDiagnosticSource source) - => _sources = _sources.Remove(source); + void IHotReloadDiagnosticManager.Unregister(IEnumerable providers) + { + foreach (var provider in providers) + _providers = _providers.Remove(provider); + } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs index 5d41c5a98860e..101865e806152 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -15,19 +15,20 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal { - internal class HotReloadDiagnosticSource(TextDocument document, IHotReloadDiagnosticSource hotReloadDiagnosticSource) : IDiagnosticSource + internal class HotReloadDiagnosticSource(IHotReloadDiagnosticSource source) : IDiagnosticSource { async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { - var diagnostics = await hotReloadDiagnosticSource.GetDocumentDiagnosticsAsync(document, cancellationToken).ConfigureAwait(false); + var diagnostics = await source.GetDiagnosticsAsync(new HotReloadRequestContext(context), cancellationToken).ConfigureAwait(false); + var document = source.TextDocument; var result = diagnostics.Select(e => DiagnosticData.Create(e, document)).ToImmutableArray(); return result; } - TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = document.GetURI() }; - ProjectOrDocumentId IDiagnosticSource.GetId() => new(document.Id); - Project IDiagnosticSource.GetProject() => document.Project; + TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = source.TextDocument.GetURI() }; + ProjectOrDocumentId IDiagnosticSource.GetId() => new(source.TextDocument.Id); + Project IDiagnosticSource.GetProject() => source.TextDocument.Project; bool IDiagnosticSource.IsLiveSource() => true; - string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {document.FilePath ?? document.Name} in {document.Project.Name}"; + string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {source.TextDocument.FilePath ?? source.TextDocument.Name} in {source.TextDocument.Project.Name}"; } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs new file mode 100644 index 0000000000000..97c7f3f663561 --- /dev/null +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; + +internal abstract class HotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager diagnosticManager, bool isDocument) : IDiagnosticSourceProvider +{ + string IDiagnosticSourceProvider.Name => "HotReloadDiagnostics"; + bool IDiagnosticSourceProvider.IsDocument => isDocument; + + async ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + var hotReloadContext = new HotReloadRequestContext(context); + List sources = new(); + foreach (var provider in diagnosticManager.Providers) + { + if (provider.IsDocument == isDocument) + { + var hotReloadSources = await provider.CreateDiagnosticSourcesAsync(hotReloadContext, cancellationToken).ConfigureAwait(false); + sources.AddRange(hotReloadSources.Select(s => new HotReloadDiagnosticSource(s))); + } + } + + var result = sources.ToImmutableArray(); + return DiagnosticSourceManager.AggregateSourcesIfNeeded(result, isDocument); + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal class DocumentHotReloadDiagnosticSourceProvider([Import] IHotReloadDiagnosticManager diagnosticManager) + : HotReloadDiagnosticSourceProvider(diagnosticManager, isDocument: true) + { + } + + [Export(typeof(IDiagnosticSourceProvider)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal class WorkspaceHotReloadDiagnosticSourceProvider([Import] IHotReloadDiagnosticManager diagnosticManager) + : HotReloadDiagnosticSourceProvider(diagnosticManager, isDocument: false) + { + } +} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs deleted file mode 100644 index c147997aac46d..0000000000000 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/WorkspaceHotReloadDiagnosticSourceProvider.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -using Microsoft.CodeAnalysis.PooledObjects; - -namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; - -[Export(typeof(IDiagnosticSourceProvider)), Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class WorkspaceHotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager hotReloadErrorService) - : AbstractHotReloadDiagnosticSourceProvider - , IDiagnosticSourceProvider -{ - bool IDiagnosticSourceProvider.IsDocument => false; - - async ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - if (context.Solution is not Solution solution) - { - return []; - } - - using var _ = ArrayBuilder.GetInstance(out var builder); - foreach (var hotReloadSource in hotReloadErrorService.Sources) - { - var docIds = await hotReloadSource.GetDocumentIdsAsync(cancellationToken).ConfigureAwait(false); - foreach (var docId in docIds) - { - if (solution.GetDocument(docId) is { } document && !context.IsTracking(document.GetURI())) - { - builder.Add(new HotReloadDiagnosticSource(document, hotReloadSource)); - } - } - } - - var result = builder.ToImmutableAndClear(); - return result; - } -} diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 368b69575bb21..70c2918b8f69c 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,35 +1,40 @@ -const Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.SourceName = "HotReloadDiagnostic" -> string! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.Diagnostics.get -> System.Collections.Immutable.ImmutableArray -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.DocumentId.get -> Microsoft.CodeAnalysis.DocumentId! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadDocumentDiagnostics.HotReloadDocumentDiagnostics(Microsoft.CodeAnalysis.DocumentId! documentId, System.Collections.Immutable.ImmutableArray diagnostics) -> void +abstract Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.ClientCapabilities.get -> Roslyn.LanguageServer.Protocol.ClientCapabilities! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.HotReloadRequestContext(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.IsTracking(Microsoft.CodeAnalysis.TextDocument! textDocument) -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.Solution.get -> Microsoft.CodeAnalysis.Solution? +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.TextDocument.get -> Microsoft.CodeAnalysis.TextDocument? Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Refresh() -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Register(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Sources.get -> System.Collections.Immutable.ImmutableArray -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Unregister(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Providers.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Register(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.RequestRefresh() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Unregister(System.Collections.Generic.IEnumerable! providers) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDocumentDiagnosticsAsync(Microsoft.CodeAnalysis.TextDocument! document, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDocumentIdsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.TextDocument.get -> Microsoft.CodeAnalysis.TextDocument! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.IsDocument.get -> bool Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStartAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.HandleDiagnosticSessionStopAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.ProcessInfo info, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.InitializeAsync(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnosticsLanguageService.RequestDataBridgeConnectionAsync(string! connectionId, System.Threading.CancellationToken token) -> System.Threading.Tasks.Task! -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.AbstractHotReloadDiagnosticSourceProvider() -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadDiagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.HotReloadDiagnosticManager(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Providers.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.TextDocument! document, Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! hotReloadDiagnosticSource) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! hotReloadErrorService) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.HotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager, bool isDocument) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry.RunningProcessEntry() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.CreateILspService(Microsoft.CodeAnalysis.LanguageServer.LspServices! lspServices, Microsoft.CodeAnalysis.LanguageServer.WellKnownLspServerKinds serverKind) -> Microsoft.CodeAnalysis.LanguageServer.ILspService! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.OnServiceBrokerInitialized(Microsoft.ServiceHub.Framework.IServiceBroker! serviceBroker) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.VisualDiagnosticsServiceFactory(Microsoft.CodeAnalysis.LanguageServer.LspWorkspaceRegistrationService! lspWorkspaceRegistrationService) -> void -static readonly Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.AbstractHotReloadDiagnosticSourceProvider.SourceNames -> System.Collections.Immutable.ImmutableArray \ No newline at end of file +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.VisualDiagnosticsServiceFactory.VisualDiagnosticsServiceFactory(Microsoft.CodeAnalysis.LanguageServer.LspWorkspaceRegistrationService! lspWorkspaceRegistrationService) -> void \ No newline at end of file From 155cf4a4a40e975d28b612e5c7135e6c588e9565 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 10:27:20 -0700 Subject: [PATCH 254/292] Rename types --- .../VisualStudioMetadataReference.Snapshot.cs | 2 +- .../VisualStudioMetadataReferenceManager.cs | 12 ++++---- .../Serialization/ISupportTemporaryStorage.cs | 2 +- .../SerializerService_Reference.cs | 22 +++++++-------- ...ageService.TemporaryStorageStreamHandle.cs | 28 +++++++++++++++++++ .../TemporaryStorageService.cs | 22 ++------------- .../ITemporaryStorageService.cs | 2 +- ...le.cs => ITemporaryStorageStreamHandle.cs} | 2 +- ...orageService.TrivialStorageStreamHandle.cs | 22 +++++++++++++++ .../TrivialTemporaryStorageService.cs | 16 ++--------- .../ProjectSystemProjectOptionsProcessor.cs | 4 +-- ...CompilationState.SkeletonReferenceCache.cs | 2 +- ...onCompilationState.SkeletonReferenceSet.cs | 4 +-- .../TemporaryStorageServiceTests.cs | 4 +-- 14 files changed, 84 insertions(+), 60 deletions(-) create mode 100644 src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs rename src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/{ITemporaryStorageHandle.cs => ITemporaryStorageStreamHandle.cs} (95%) create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs index 81388674db446..761a41ea3d178 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReference.Snapshot.cs @@ -110,7 +110,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private string GetDebuggerDisplay() => "Metadata File: " + FilePath; - public IReadOnlyList StorageHandles + public IReadOnlyList StorageHandles => _provider.GetStorageHandles(this.FilePath, _timestamp.Value); } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs index 6fa856f0c688f..01a6a41f6695d 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs @@ -45,7 +45,7 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS /// name/offset/length to the remote process, and it can map that same memory in directly, instead of needing the /// host to send the entire contents of the assembly over the channel to the OOP process. /// - private static readonly ConditionalWeakTable> s_metadataToStorageHandles = new(); + private static readonly ConditionalWeakTable> s_metadataToStorageHandles = new(); private readonly MetadataCache _metadataCache = new(); private readonly ImmutableArray _runtimeDirectories; @@ -88,7 +88,7 @@ public void Dispose() } } - public IReadOnlyList? GetStorageHandles(string fullPath, DateTime snapshotTimestamp) + public IReadOnlyList? GetStorageHandles(string fullPath, DateTime snapshotTimestamp) { var key = new FileKey(fullPath, snapshotTimestamp); // check existing metadata @@ -161,7 +161,7 @@ AssemblyMetadata GetMetadataWorker() } } - private (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) GetMetadataFromTemporaryStorage( + private (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) GetMetadataFromTemporaryStorage( FileKey moduleFileKey) { GetStorageInfoFromTemporaryStorage(moduleFileKey, out var storageHandle, out var stream); @@ -176,7 +176,7 @@ AssemblyMetadata GetMetadataWorker() } void GetStorageInfoFromTemporaryStorage( - FileKey moduleFileKey, out TemporaryStorageHandle storageHandle, out UnmanagedMemoryStream stream) + FileKey moduleFileKey, out TemporaryStorageStreamHandle storageHandle, out UnmanagedMemoryStream stream) { int size; @@ -301,12 +301,12 @@ bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)] /// private static AssemblyMetadata CreateAssemblyMetadata( FileKey fileKey, - Func moduleMetadataFactory) + Func moduleMetadataFactory) { var (manifestModule, manifestHandle) = moduleMetadataFactory(fileKey); using var _1 = ArrayBuilder.GetInstance(out var moduleBuilder); - using var _2 = ArrayBuilder.GetInstance(out var storageHandles); + using var _2 = ArrayBuilder.GetInstance(out var storageHandles); string? assemblyDir = null; foreach (var moduleName in manifestModule.GetModuleNames()) diff --git a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs index 84dc4dc0834bd..a7c02bba2bfc5 100644 --- a/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Serialization/ISupportTemporaryStorage.cs @@ -14,5 +14,5 @@ namespace Microsoft.CodeAnalysis.Serialization; /// internal interface ISupportTemporaryStorage { - IReadOnlyList? StorageHandles { get; } + IReadOnlyList? StorageHandles { get; } } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 031ce659fd857..e958468cd5861 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -309,7 +309,7 @@ private static void WriteTo(Metadata? metadata, ObjectWriter writer, Cancellatio private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageTo( PortableExecutableReference reference, - IReadOnlyList handles, + IReadOnlyList handles, ObjectWriter writer, CancellationToken cancellationToken) { @@ -329,7 +329,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return true; } - private (Metadata metadata, ImmutableArray storageHandles)? TryReadMetadataFrom( + private (Metadata metadata, ImmutableArray storageHandles)? TryReadMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { var imageKind = reader.ReadInt32(); @@ -345,7 +345,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT var count = reader.ReadInt32(); var allMetadata = new FixedSizeArrayBuilder(count); - var allHandles = new FixedSizeArrayBuilder(count); + var allHandles = new FixedSizeArrayBuilder(count); for (var i = 0; i < count; i++) { @@ -369,7 +369,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT } } - private (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFrom( + private (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFrom( ObjectReader reader, SerializationKinds kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -380,7 +380,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT ? ReadModuleMetadataFromBits() : ReadModuleMetadataFromMemoryMappedFile(); - (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromMemoryMappedFile() + (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFromMemoryMappedFile() { // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. @@ -389,7 +389,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return ReadModuleMetadataFromStorage(storageHandle); } - (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromBits() + (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFromBits() { // Host is sending us all the data as bytes. Take that and write that out to a memory mapped file on the // server side so that we can refer to this data uniformly. @@ -402,8 +402,8 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT return ReadModuleMetadataFromStorage(storageHandle); } - (ModuleMetadata metadata, TemporaryStorageHandle storageHandle) ReadModuleMetadataFromStorage( - TemporaryStorageHandle storageHandle) + (ModuleMetadata metadata, TemporaryStorageStreamHandle storageHandle) ReadModuleMetadataFromStorage( + TemporaryStorageStreamHandle storageHandle) { // Now read in the module data using that identifier. This will either be reading from the host's memory if // they passed us the information about that memory segment. Or it will be reading from our own memory if they @@ -504,16 +504,16 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere private sealed class SerializedMetadataReference : PortableExecutableReference, ISupportTemporaryStorage { private readonly Metadata _metadata; - private readonly ImmutableArray _storageHandles; + private readonly ImmutableArray _storageHandles; private readonly DocumentationProvider _provider; - public IReadOnlyList StorageHandles => _storageHandles; + public IReadOnlyList StorageHandles => _storageHandles; public SerializedMetadataReference( MetadataReferenceProperties properties, string? fullPath, Metadata metadata, - ImmutableArray storageHandles, + ImmutableArray storageHandles, DocumentationProvider initialDocumentation) : base(properties, fullPath, initialDocumentation) { diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs new file mode 100644 index 0000000000000..62762d0ee869b --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.IO.MemoryMappedFiles; +using System.Threading; + +namespace Microsoft.CodeAnalysis.Host; + +internal sealed partial class TemporaryStorageService +{ + public sealed class TemporaryStorageStreamHandle( + TemporaryStorageService storageService, MemoryMappedFile memoryMappedFile, TemporaryStorageIdentifier identifier) : ITemporaryStorageStreamHandle + { + public TemporaryStorageIdentifier Identifier => identifier; + + Stream ITemporaryStorageStreamHandle.ReadFromTemporaryStorage(CancellationToken cancellationToken) + => ReadFromTemporaryStorage(cancellationToken); + + public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) + { + var storage = new TemporaryStreamStorage( + storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); + return storage.ReadStream(cancellationToken); + } + } +} diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index 87c1bc80d50c9..b537a951e38bd 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -101,10 +101,10 @@ public TemporaryTextStorage AttachTemporaryTextStorage( string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); - ITemporaryStorageHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) => WriteToTemporaryStorage(stream, cancellationToken); - public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + public TemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; var storage = new TemporaryStreamStorage(this); @@ -113,7 +113,7 @@ public TemporaryStorageHandle WriteToTemporaryStorage(Stream stream, Cancellatio return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); } - internal TemporaryStorageHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) + internal TemporaryStorageStreamHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) { var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); return new(this, memoryMappedFile, storageIdentifier); @@ -164,22 +164,6 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) public static string CreateUniqueName(long size) => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); - public sealed class TemporaryStorageHandle( - TemporaryStorageService storageService, MemoryMappedFile memoryMappedFile, TemporaryStorageIdentifier identifier) : ITemporaryStorageHandle - { - public TemporaryStorageIdentifier Identifier => identifier; - - Stream ITemporaryStorageHandle.ReadFromTemporaryStorage(CancellationToken cancellationToken) - => ReadFromTemporaryStorage(cancellationToken); - - public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) - { - var storage = new TemporaryStreamStorage( - storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); - return storage.ReadStream(cancellationToken); - } - } - public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName { private readonly TemporaryStorageService _service; diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index 9b86919667133..b9d1f9d530efc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -44,7 +44,7 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// cref="Stream.Position"/> 0 within this method. The caller does not need to reset the stream /// itself. /// - ITemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); + ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); ITemporaryTextStorageInternal CreateTemporaryTextStorage(); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs similarity index 95% rename from src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs rename to src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index 3d2be4b29e531..e4689f2ba1381 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Host; /// to temporary storage and get a handle to it. Use to read the data back in /// any process. /// -internal interface ITemporaryStorageHandle +internal interface ITemporaryStorageStreamHandle { public TemporaryStorageIdentifier Identifier { get; } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs new file mode 100644 index 0000000000000..2b750b62a5807 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Threading; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis; + +internal sealed partial class TrivialTemporaryStorageService +{ + private sealed class TrivialStorageStreamHandle( + TemporaryStorageIdentifier storageIdentifier, + StreamStorage streamStorage) : ITemporaryStorageStreamHandle + { + public TemporaryStorageIdentifier Identifier => storageIdentifier; + + public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) + => streamStorage.ReadStream(); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 28f2b4ba4c66e..7b09b0d5bc9df 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis; -internal sealed class TrivialTemporaryStorageService : ITemporaryStorageServiceInternal +internal sealed partial class TrivialTemporaryStorageService : ITemporaryStorageServiceInternal { public static readonly TrivialTemporaryStorageService Instance = new(); @@ -24,26 +24,16 @@ private TrivialTemporaryStorageService() public ITemporaryTextStorageInternal CreateTemporaryTextStorage() => new TextStorage(); - public ITemporaryStorageHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) + public ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; var storage = new StreamStorage(); storage.WriteStream(stream); var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: stream.Length); - var handle = new TrivialStorageHandle(identifier, storage); + var handle = new TrivialStorageStreamHandle(identifier, storage); return handle; } - private sealed class TrivialStorageHandle( - TemporaryStorageIdentifier storageIdentifier, - StreamStorage streamStorage) : ITemporaryStorageHandle - { - public TemporaryStorageIdentifier Identifier => storageIdentifier; - - public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) - => streamStorage.ReadStream(); - } - private sealed class StreamStorage { private MemoryStream? _stream; diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index 950cbab4a7b8a..9c2059d1006c3 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -38,7 +38,7 @@ internal class ProjectSystemProjectOptionsProcessor : IDisposable /// (especially in cases with many references). /// /// Note: this will be null in the case that the command line is an empty array. - private ITemporaryStorageHandle? _commandLineStorageHandle; + private ITemporaryStorageStreamHandle? _commandLineStorageHandle; private CommandLineArguments _commandLineArgumentsForCommandLine; private string? _explicitRuleSetFilePath; @@ -254,7 +254,7 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) } static IEnumerable EnumerateLines( - ITemporaryStorageHandle storageHandle) + ITemporaryStorageStreamHandle storageHandle) { using var stream = storageHandle.ReadFromTemporaryStorage(CancellationToken.None); using var reader = new StreamReader(stream); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs index 0735a5f50c9cf..2fbf3bc719d90 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceCache.cs @@ -230,7 +230,7 @@ public readonly SkeletonReferenceCache Clone() compilation.AssemblyName, new DeferredDocumentationProvider(compilation)); - (AssemblyMetadata? metadata, ITemporaryStorageHandle storageHandle) TryCreateMetadataAndHandle() + (AssemblyMetadata? metadata, ITemporaryStorageStreamHandle storageHandle) TryCreateMetadataAndHandle() { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs index 9d579a586ddf3..e3e9b84b07744 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.SkeletonReferenceSet.cs @@ -19,7 +19,7 @@ internal partial class SolutionCompilationState /// private sealed class SkeletonReferenceSet( AssemblyMetadata metadata, - ITemporaryStorageHandle storageHandle, + ITemporaryStorageStreamHandle storageHandle, string? assemblyName, DeferredDocumentationProvider documentationProvider) { @@ -31,7 +31,7 @@ private sealed class SkeletonReferenceSet( /// private readonly Dictionary _referenceMap = []; - public ITemporaryStorageHandle StorageHandle => storageHandle; + public ITemporaryStorageStreamHandle StorageHandle => storageHandle; public PortableExecutableReference GetOrCreateMetadataReference(MetadataReferenceProperties properties) { diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index b859c52a4c186..2bd1e0f417690 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -116,7 +116,7 @@ public void TestZeroLengthStreams() var service = Assert.IsType(workspace.Services.GetRequiredService()); // 0 length streams are allowed - TemporaryStorageHandle handle; + TemporaryStorageStreamHandle handle; using (var stream1 = new MemoryStream()) { handle = service.WriteToTemporaryStorage(stream1, CancellationToken.None); @@ -185,7 +185,7 @@ public void TestTemporaryStorageScaling() // Create 4GB of memory mapped files var fileCount = (int)((long)4 * 1024 * 1024 * 1024 / data.Length); - var storageHandles = new List(fileCount); + var storageHandles = new List(fileCount); for (var i = 0; i < fileCount; i++) { var handle = service.WriteToTemporaryStorage(data, CancellationToken.None); From 0abbdda012b38d4e0245edb9d54e333b16544046 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 10:36:56 -0700 Subject: [PATCH 255/292] Add initial stubs and trivial impl --- .../TemporaryStorage/ITemporaryStorage.cs | 14 ++++---- .../ITemporaryStorageService.cs | 5 ++- .../ITemporaryStorageStreamHandle.cs | 16 +++++++-- ...StorageService.TrivialStorageTextHandle.cs | 26 ++++++++++++++ .../TrivialTemporaryStorageService.cs | 34 +++++++++---------- 5 files changed, 66 insertions(+), 29 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index aaac0a3ac1e6c..8e4dde76b2938 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -33,10 +33,10 @@ public interface ITemporaryStreamStorage : IDisposable /// /// TemporaryStorage can be used to read and write text to a temporary storage location. /// -internal interface ITemporaryTextStorageInternal -{ - SourceText ReadText(CancellationToken cancellationToken = default); - Task ReadTextAsync(CancellationToken cancellationToken = default); - void WriteText(SourceText text, CancellationToken cancellationToken = default); - Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); -} +//internal interface ITemporaryTextStorageInternal +//{ +// SourceText ReadText(CancellationToken cancellationToken = default); +// Task ReadTextAsync(CancellationToken cancellationToken = default); +// void WriteText(SourceText text, CancellationToken cancellationToken = default); +// Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); +//} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index b9d1f9d530efc..c0c7b5c80f731 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -5,6 +5,8 @@ using System; using System.IO; using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Host; @@ -46,5 +48,6 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); - ITemporaryTextStorageInternal CreateTemporaryTextStorage(); + ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken); + Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index e4689f2ba1381..460241f660790 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -4,15 +4,17 @@ using System.IO; using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Host; /// /// Represents a handle to data stored to temporary storage (generally a memory mapped file). As long as this handle is /// alive, the data should remain in storage and can be readable from any process using the information provided in . Use to write the data -/// to temporary storage and get a handle to it. Use to read the data back in -/// any process. +/// cref="Identifier"/>. Use to write the data to temporary storage and get a handle to it. Use to read the data back in any process. /// internal interface ITemporaryStorageStreamHandle { @@ -24,3 +26,11 @@ internal interface ITemporaryStorageStreamHandle /// Stream ReadFromTemporaryStorage(CancellationToken cancellationToken); } + +internal interface ITemporaryStorageTextHandle +{ + public TemporaryStorageIdentifier Identifier { get; } + + SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); + Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs new file mode 100644 index 0000000000000..9bef5f0738b8c --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis; + +internal sealed partial class TrivialTemporaryStorageService +{ + private sealed class TrivialStorageTextHandle( + TemporaryStorageIdentifier identifier, + TextStorage storage) : ITemporaryStorageTextHandle + { + public TemporaryStorageIdentifier Identifier => identifier; + + public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) + => storage.ReadText(); + + public Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken) + => Task.FromResult(ReadFromTemporaryStorage(cancellationToken)); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 7b09b0d5bc9df..113636d000d9d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -4,12 +4,10 @@ using System; using System.IO; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -21,8 +19,17 @@ private TrivialTemporaryStorageService() { } - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - => new TextStorage(); + public ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + { + var storage = new TextStorage(); + storage.WriteText(text); + var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length); + var handle = new TrivialStorageTextHandle(identifier, storage); + return handle; + } + + public Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + => Task.FromResult(WriteToTemporaryStorage(text, cancellationToken)); public ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { @@ -59,20 +66,17 @@ public void WriteStream(Stream stream) } } - private sealed class TextStorage : ITemporaryTextStorageInternal + private sealed class TextStorage { private SourceText? _sourceText; - public void Dispose() - => _sourceText = null; - - public SourceText ReadText(CancellationToken cancellationToken) + public SourceText ReadText() => _sourceText ?? throw new InvalidOperationException(); - public Task ReadTextAsync(CancellationToken cancellationToken) - => Task.FromResult(ReadText(cancellationToken)); + public Task ReadTextAsync() + => Task.FromResult(ReadText()); - public void WriteText(SourceText text, CancellationToken cancellationToken) + public void WriteText(SourceText text) { // This is a trivial implementation, indeed. Note, however, that we retain a strong // reference to the source text, which defeats the intent of RecoverableTextAndVersion, but @@ -81,11 +85,5 @@ public void WriteText(SourceText text, CancellationToken cancellationToken) if (existingValue is not null) throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); } - - public Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default) - { - WriteText(text, cancellationToken); - return Task.CompletedTask; - } } } From be63421fb3afcd97b8e8f366a7193cdf1fdbeb86 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 10:59:22 -0700 Subject: [PATCH 256/292] Flesh out --- ...eService.DirectMemoryAccessStreamReader.cs | 76 ++++++ ...ageService.TemporaryStorageStreamHandle.cs | 15 +- .../TemporaryStorageService.cs | 242 ++++++++---------- .../ITemporaryStorageStreamHandle.cs | 25 +- .../ITemporaryStorageWithName.cs | 40 +-- ...StorageService.TrivialStorageTextHandle.cs | 4 +- .../TrivialTemporaryStorageService.cs | 2 +- 7 files changed, 235 insertions(+), 169 deletions(-) create mode 100644 src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs new file mode 100644 index 0000000000000..9918414654ea1 --- /dev/null +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.DirectMemoryAccessStreamReader.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +internal sealed partial class TemporaryStorageService +{ + private unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength + { + private char* _position; + private readonly char* _end; + + public DirectMemoryAccessStreamReader(char* src, int length) + : base(length) + { + RoslynDebug.Assert(src != null); + RoslynDebug.Assert(length >= 0); + + _position = src; + _end = _position + length; + } + + public override int Peek() + { + if (_position >= _end) + { + return -1; + } + + return *_position; + } + + public override int Read() + { + if (_position >= _end) + { + return -1; + } + + return *_position++; + } + + public override int Read(char[] buffer, int index, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0 || index >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0 || (index + count) > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + count = Math.Min(count, (int)(_end - _position)); + if (count > 0) + { + Marshal.Copy((IntPtr)_position, buffer, index, count); + _position += count; + } + + return count; + } + } +} diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs index 62762d0ee869b..7b06db02ddbff 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.TemporaryStorageStreamHandle.cs @@ -5,13 +5,16 @@ using System.IO; using System.IO.MemoryMappedFiles; using System.Threading; +using Microsoft.CodeAnalysis.Internal.Log; namespace Microsoft.CodeAnalysis.Host; internal sealed partial class TemporaryStorageService { public sealed class TemporaryStorageStreamHandle( - TemporaryStorageService storageService, MemoryMappedFile memoryMappedFile, TemporaryStorageIdentifier identifier) : ITemporaryStorageStreamHandle + MemoryMappedFile memoryMappedFile, + TemporaryStorageIdentifier identifier) + : ITemporaryStorageStreamHandle { public TemporaryStorageIdentifier Identifier => identifier; @@ -20,9 +23,13 @@ Stream ITemporaryStorageStreamHandle.ReadFromTemporaryStorage(CancellationToken public UnmanagedMemoryStream ReadFromTemporaryStorage(CancellationToken cancellationToken) { - var storage = new TemporaryStreamStorage( - storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); - return storage.ReadStream(cancellationToken); + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var info = new MemoryMappedInfo(memoryMappedFile, Identifier.Name, Identifier.Offset, Identifier.Size); + return info.CreateReadableStream(); + } } } } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index b537a951e38bd..c349cadcd41e4 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -94,12 +94,39 @@ private TemporaryStorageService(IWorkspaceThreadingService? workspaceThreadingSe _textFactory = textFactory; } - public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - => new TemporaryTextStorage(this); + //public ITemporaryTextStorageInternal CreateTemporaryTextStorage() + // => new TemporaryTextStorage(this); - public TemporaryTextStorage AttachTemporaryTextStorage( - string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) - => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); + //public TemporaryTextStorage AttachTemporaryTextStorage( + // string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) + // => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); + + ITemporaryStorageTextHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + => WriteToTemporaryStorage(text, cancellationToken); + + async Task ITemporaryStorageServiceInternal.WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + => await WriteToTemporaryStorageAsync(text, cancellationToken).ConfigureAwait(false); + + public TemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) + { + var storage = new TemporaryTextStorage(this); + storage.WriteText(text, cancellationToken); + return CreateHandleFromStorage(storage); + } + + public async Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) + { + var storage = new TemporaryTextStorage(this); + await storage.WriteTextAsync(text, cancellationToken).ConfigureAwait(false); + return CreateHandleFromStorage(storage); + } + + private TemporaryStorageTextHandle CreateHandleFromStorage(TemporaryTextStorage storage) + { + var identifier = new TemporaryStorageTextIdentifier( + storage.Name, storage.Offset, storage.Size, storage.ChecksumAlgorithm, storage.Encoding); + return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); + } ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) => WriteToTemporaryStorage(stream, cancellationToken); @@ -110,13 +137,13 @@ public TemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, Cance var storage = new TemporaryStreamStorage(this); storage.WriteStream(stream, cancellationToken); var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); - return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); + return new(storage.MemoryMappedInfo.MemoryMappedFile, identifier); } - internal TemporaryStorageStreamHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) + internal static TemporaryStorageStreamHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) { var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); - return new(this, memoryMappedFile, storageIdentifier); + return new(memoryMappedFile, storageIdentifier); } /// @@ -164,7 +191,67 @@ private MemoryMappedInfo CreateTemporaryStorage(long size) public static string CreateUniqueName(long size) => "Roslyn Temp Storage " + size.ToString() + " " + Guid.NewGuid().ToString("N"); - public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITemporaryStorageWithName + public sealed class TemporaryStorageTextHandle( + TemporaryStorageService storageService, + MemoryMappedFile memoryMappedFile, + TemporaryStorageTextIdentifier identifier) + : ITemporaryStorageTextHandle + { + public TemporaryStorageTextIdentifier Identifier => identifier; + + public async Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken) + { + // There is a reason for implementing it like this: proper async implementation + // that reads the underlying memory mapped file stream in an asynchronous fashion + // doesn't actually work. Windows doesn't offer + // any non-blocking way to read from a memory mapped file; the underlying memcpy + // may block as the memory pages back in and that's something you have to live + // with. Therefore, any implementation that attempts to use async will still + // always be blocking at least one threadpool thread in the memcpy in the case + // of a page fault. Therefore, if we're going to be blocking a thread, we should + // just block one thread and do the whole thing at once vs. a fake "async" + // implementation which will continue to requeue work back to the thread pool. + if (storageService._workspaceThreadingService is { IsOnMainThread: true }) + { + await Task.Yield().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + return ReadFromTemporaryStorage(cancellationToken); + } + + public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) + { + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) + { + var info = new MemoryMappedInfo(memoryMappedFile, Identifier.Name, Identifier.Offset, Identifier.Size); + using var stream = info.CreateReadableStream(); + using var reader = CreateTextReaderFromTemporaryStorage(stream); + + // we pass in encoding we got from original source text even if it is null. + return storageService._textFactory.CreateText(reader, Identifier.Encoding, Identifier.ChecksumAlgorithm, cancellationToken); + } + } + + private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) + { + var src = (char*)stream.PositionPointer; + + // BOM: Unicode, little endian + // Skip the BOM when creating the reader + Debug.Assert(*src == 0xFEFF); + + return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); + } + +#if false + var storage = new TemporaryStreamStorage( + storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); + return storage.ReadStream(cancellationToken); +#endif + } + + public sealed class TemporaryTextStorage // : ITemporaryTextStorageInternal, ITemporaryStorageWithName { private readonly TemporaryStorageService _service; private SourceHashAlgorithm _checksumAlgorithm; @@ -172,6 +259,8 @@ public sealed class TemporaryTextStorage : ITemporaryTextStorageInternal, ITempo private ImmutableArray _contentHash; private MemoryMappedInfo? _memoryMappedInfo; + public MemoryMappedInfo MemoryMappedInfo => _memoryMappedInfo ?? throw new InvalidOperationException(); + public TemporaryTextStorage(TemporaryStorageService service) => _service = service; @@ -191,11 +280,9 @@ public TemporaryTextStorage( _memoryMappedInfo = MemoryMappedInfo.OpenExisting(storageName, offset, size); } - // TODO: cleanup https://github.com/dotnet/roslyn/issues/43037 - // Offset, Size not accessed if Name is null - public string? Name => _memoryMappedInfo?.Name; - public long Offset => _memoryMappedInfo!.Offset; - public long Size => _memoryMappedInfo!.Size; + public string Name => this.MemoryMappedInfo.Name; + public long Offset => this.MemoryMappedInfo.Offset; + public long Size => this.MemoryMappedInfo.Size; /// /// Gets the value for the property for the @@ -215,44 +302,6 @@ public TemporaryTextStorage( /// public ImmutableArray ContentHash => _contentHash; - public SourceText ReadText(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) - { - using var stream = _memoryMappedInfo.CreateReadableStream(); - using var reader = CreateTextReaderFromTemporaryStorage(stream); - - // we pass in encoding we got from original source text even if it is null. - return _service._textFactory.CreateText(reader, _encoding, _checksumAlgorithm, cancellationToken); - } - } - - public async Task ReadTextAsync(CancellationToken cancellationToken) - { - // There is a reason for implementing it like this: proper async implementation - // that reads the underlying memory mapped file stream in an asynchronous fashion - // doesn't actually work. Windows doesn't offer - // any non-blocking way to read from a memory mapped file; the underlying memcpy - // may block as the memory pages back in and that's something you have to live - // with. Therefore, any implementation that attempts to use async will still - // always be blocking at least one threadpool thread in the memcpy in the case - // of a page fault. Therefore, if we're going to be blocking a thread, we should - // just block one thread and do the whole thing at once vs. a fake "async" - // implementation which will continue to requeue work back to the thread pool. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - } - - return ReadText(cancellationToken); - } - public void WriteText(SourceText text, CancellationToken cancellationToken) { if (_memoryMappedInfo != null) @@ -289,17 +338,6 @@ public async Task WriteTextAsync(SourceText text, CancellationToken cancellation WriteText(text, cancellationToken); } - - private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) - { - var src = (char*)stream.PositionPointer; - - // BOM: Unicode, little endian - // Skip the BOM when creating the reader - Debug.Assert(*src == 0xFEFF); - - return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); - } } internal sealed class TemporaryStreamStorage @@ -323,21 +361,6 @@ public TemporaryStreamStorage( public long Offset => this.MemoryMappedInfo.Offset; public long Size => this.MemoryMappedInfo.Size; - public UnmanagedMemoryStream ReadStream(CancellationToken cancellationToken) - { - if (_memoryMappedInfo == null) - { - throw new InvalidOperationException(); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadStream, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - - return _memoryMappedInfo.CreateReadableStream(); - } - } - public void WriteStream(Stream stream, CancellationToken cancellationToken) { if (_memoryMappedInfo != null) @@ -363,66 +386,3 @@ public void WriteStream(Stream stream, CancellationToken cancellationToken) } } } - -internal unsafe class DirectMemoryAccessStreamReader : TextReaderWithLength -{ - private char* _position; - private readonly char* _end; - - public DirectMemoryAccessStreamReader(char* src, int length) - : base(length) - { - RoslynDebug.Assert(src != null); - RoslynDebug.Assert(length >= 0); - - _position = src; - _end = _position + length; - } - - public override int Peek() - { - if (_position >= _end) - { - return -1; - } - - return *_position; - } - - public override int Read() - { - if (_position >= _end) - { - return -1; - } - - return *_position++; - } - - public override int Read(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (index < 0 || index >= buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (count < 0 || (index + count) > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - count = Math.Min(count, (int)(_end - _position)); - if (count > 0) - { - Marshal.Copy((IntPtr)_position, buffer, index, count); - _position += count; - } - - return count; - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index 460241f660790..2d432b715b2b2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -3,9 +3,11 @@ // See the LICENSE file in the project root for more information. using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -29,8 +31,29 @@ internal interface ITemporaryStorageStreamHandle internal interface ITemporaryStorageTextHandle { - public TemporaryStorageIdentifier Identifier { get; } + public TemporaryStorageTextIdentifier Identifier { get; } SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); } + +internal sealed record TemporaryStorageTextIdentifier( + string Name, long Offset, long Size, SourceHashAlgorithm ChecksumAlgorithm, Encoding? Encoding) +{ + public static TemporaryStorageTextIdentifier ReadFrom(ObjectReader reader) + => new( + reader.ReadRequiredString(), + reader.ReadInt64(), + reader.ReadInt64(), + (SourceHashAlgorithm)reader.ReadInt32(), + reader.ReadEncoding()); + + public void WriteTo(ObjectWriter writer) + { + writer.WriteString(Name); + writer.WriteInt64(Offset); + writer.WriteInt64(Size); + writer.WriteInt32((int)ChecksumAlgorithm); + writer.WriteEncoding(Encoding); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs index 3d635adbe6c8d..ab3f73b119383 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs @@ -4,26 +4,26 @@ namespace Microsoft.CodeAnalysis.Host; -/// -/// TemporaryStorage can be used to read and write text to a temporary storage location. -/// -internal interface ITemporaryStorageWithName -{ - // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 - // Name shouldn't be nullable. +///// +///// TemporaryStorage can be used to read and write text to a temporary storage location. +///// +//internal interface ITemporaryStorageWithName +//{ +// // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 +// // Name shouldn't be nullable. - /// - /// Get name of the temporary storage - /// - string? Name { get; } +// /// +// /// Get name of the temporary storage +// /// +// string? Name { get; } - /// - /// Get offset of the temporary storage - /// - long Offset { get; } +// /// +// /// Get offset of the temporary storage +// /// +// long Offset { get; } - /// - /// Get size of the temporary storage - /// - long Size { get; } -} +// /// +// /// Get size of the temporary storage +// /// +// long Size { get; } +//} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs index 9bef5f0738b8c..c53bf7dff6813 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs @@ -12,10 +12,10 @@ namespace Microsoft.CodeAnalysis; internal sealed partial class TrivialTemporaryStorageService { private sealed class TrivialStorageTextHandle( - TemporaryStorageIdentifier identifier, + TemporaryStorageTextIdentifier identifier, TextStorage storage) : ITemporaryStorageTextHandle { - public TemporaryStorageIdentifier Identifier => identifier; + public TemporaryStorageTextIdentifier Identifier => identifier; public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) => storage.ReadText(); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 113636d000d9d..b342ad6bbcce0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -23,7 +23,7 @@ public ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, Canc { var storage = new TextStorage(); storage.WriteText(text); - var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length); + var identifier = new TemporaryStorageTextIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length, text.ChecksumAlgorithm, text.Encoding); var handle = new TrivialStorageTextHandle(identifier, storage); return handle; } From f1faa2c2a2b7222f44ea2d5abe789384c08c8ae2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 11:22:58 -0700 Subject: [PATCH 257/292] Flesh out --- .../Test/Workspaces/TextFactoryTests.cs | 12 ++-- .../Serialization/SerializableSourceText.cs | 62 ++++++++----------- .../SerializerService_Reference.cs | 2 +- .../TemporaryStorageService.cs | 10 ++- .../TemporaryStorage/ITemporaryStorage.cs | 11 ---- .../ITemporaryStorageStreamHandle.cs | 33 ---------- .../ITemporaryStorageTextHandle.cs | 17 +++++ .../TemporaryStorageTextIdentifier.cs | 43 +++++++++++++ .../TrivialTemporaryStorageService.cs | 3 +- .../Solution/RecoverableTextAndVersion.cs | 25 ++++---- .../Workspace/Solution/TextDocumentState.cs | 4 +- .../TemporaryStorageServiceTests.cs | 28 +-------- 12 files changed, 117 insertions(+), 133 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs create mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs diff --git a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs index aa516e6d36f6e..377d303f6a210 100644 --- a/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/TextFactoryTests.cs @@ -85,13 +85,11 @@ public async Task TestCreateFromTemporaryStorage() var text = SourceText.From("Hello, World!"); - // Create a temporary storage location - var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it - await temporaryStorage.WriteTextAsync(text); + var handle = await temporaryStorageService.WriteToTemporaryStorageAsync(text, CancellationToken.None); // Read text back from it - var text2 = await temporaryStorage.ReadTextAsync(); + var text2 = await handle.ReadFromTemporaryStorageAsync(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); @@ -107,13 +105,11 @@ public async Task TestCreateFromTemporaryStorageWithEncoding() var text = SourceText.From("Hello, World!", Encoding.ASCII); - // Create a temporary storage location - var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); // Write text into it - await temporaryStorage.WriteTextAsync(text); + var handle = await temporaryStorageService.WriteToTemporaryStorageAsync(text, CancellationToken.None); // Read text back from it - var text2 = await temporaryStorage.ReadTextAsync(); + var text2 = await handle.ReadFromTemporaryStorageAsync(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index 37bc567b3dd73..c40bfa5cbc108 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -33,20 +33,20 @@ internal sealed class SerializableSourceText /// The storage location for . /// /// - /// Exactly one of or will be non-. + /// Exactly one of or will be non-. /// - private readonly TemporaryTextStorage? _storage; + private readonly TemporaryStorageTextHandle? _storageHandle; /// /// The in the current process. /// /// - /// + /// /// private readonly SourceText? _text; /// - /// Weak reference to a SourceText computed from . Useful so that if multiple requests + /// Weak reference to a SourceText computed from . Useful so that if multiple requests /// come in for the source text, the same one can be returned as long as something is holding it alive. /// private readonly WeakReference _computedText = new(target: null); @@ -56,26 +56,26 @@ internal sealed class SerializableSourceText /// public readonly Checksum ContentChecksum; - public SerializableSourceText(TemporaryTextStorage storage, ImmutableArray contentHash) - : this(storage, text: null, contentHash) + public SerializableSourceText(TemporaryStorageTextHandle storageHandle) + : this(storageHandle, text: null, storageHandle.Identifier.ContentHash) { } public SerializableSourceText(SourceText text, ImmutableArray contentHash) - : this(storage: null, text, contentHash) + : this(storageHandle: null, text, contentHash) { } - private SerializableSourceText(TemporaryTextStorage? storage, SourceText? text, ImmutableArray contentHash) + private SerializableSourceText(TemporaryStorageTextHandle? storageHandle, SourceText? text, ImmutableArray contentHash) { - Debug.Assert(storage is null != text is null); + Debug.Assert(storageHandle is null != text is null); - _storage = storage; + _storageHandle = storageHandle; _text = text; ContentChecksum = Checksum.Create(contentHash); #if DEBUG - var computedContentHash = TryGetText()?.GetContentHash() ?? _storage!.ContentHash; + var computedContentHash = TryGetText()?.GetContentHash() ?? _storageHandle!.Identifier.ContentHash; Debug.Assert(contentHash.SequenceEqual(computedContentHash)); #endif } @@ -95,7 +95,7 @@ public async ValueTask GetTextAsync(CancellationToken cancellationTo return text; // Read and cache the text from the storage object so that other requests may see it if still kept alive by something. - text = await _storage!.ReadTextAsync(cancellationToken).ConfigureAwait(false); + text = await _storageHandle!.ReadFromTemporaryStorageAsync(cancellationToken).ConfigureAwait(false); _computedText.SetTarget(text); return text; } @@ -107,7 +107,7 @@ public SourceText GetText(CancellationToken cancellationToken) return text; // Read and cache the text from the storage object so that other requests may see it if still kept alive by something. - text = _storage!.ReadText(cancellationToken); + text = _storageHandle!.ReadFromTemporaryStorage(cancellationToken); _computedText.SetTarget(text); return text; } @@ -120,10 +120,10 @@ public static ValueTask FromTextDocumentStateAsync( // If we're already pointing at a serializable loader, we can just use that directly. return new(serializableLoader.SerializableSourceText); } - else if (state.Storage is TemporaryTextStorage storage) + else if (state.StorageHandle is TemporaryStorageTextHandle storageHandle) { // Otherwise, if we're pointing at a memory mapped storage location, we can create the source text that directly wraps that. - return new(new SerializableSourceText(storage, storage.ContentHash)); + return new(new SerializableSourceText(storageHandle)); } else { @@ -142,58 +142,48 @@ public static ValueTask FromTextDocumentStateAsync( public void Serialize(ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - if (_storage is not null) + if (_storageHandle is not null) { - writer.WriteInt32((int)_storage.ChecksumAlgorithm); - writer.WriteEncoding(_storage.Encoding); - writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_storage.ContentHash)!); - writer.WriteInt32((int)SerializationKinds.MemoryMapFile); - writer.WriteString(_storage.Name); - writer.WriteInt64(_storage.Offset); - writer.WriteInt64(_storage.Size); + _storageHandle.Identifier.WriteTo(writer); } else { RoslynDebug.AssertNotNull(_text); + writer.WriteInt32((int)SerializationKinds.Bits); writer.WriteInt32((int)_text.ChecksumAlgorithm); writer.WriteEncoding(_text.Encoding); writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_text.GetContentHash())!); - writer.WriteInt32((int)SerializationKinds.Bits); _text.WriteTo(writer, cancellationToken); } } public static SerializableSourceText Deserialize( ObjectReader reader, - ITemporaryStorageServiceInternal storageService, + TemporaryStorageService storageService, ITextFactoryService textService, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); - var encoding = reader.ReadEncoding(); - var contentHash = ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray()); - var kind = (SerializationKinds)reader.ReadInt32(); Contract.ThrowIfFalse(kind is SerializationKinds.Bits or SerializationKinds.MemoryMapFile); if (kind == SerializationKinds.MemoryMapFile) { - var storage2 = (TemporaryStorageService)storageService; - - var name = reader.ReadRequiredString(); - var offset = reader.ReadInt64(); - var size = reader.ReadInt64(); + var identifier = TemporaryStorageTextIdentifier.ReadFrom(reader); + var storageHandle = storageService.GetHandle(identifier); - var storage = storage2.AttachTemporaryTextStorage(name, offset, size, checksumAlgorithm, encoding, contentHash); - return new SerializableSourceText(storage, contentHash); + return new SerializableSourceText(storageHandle); } else { + var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); + var encoding = reader.ReadEncoding(); + var contentHash = ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray()); + return new SerializableSourceText( SourceTextExtensions.ReadFrom(textService, reader, encoding, checksumAlgorithm, cancellationToken), contentHash); diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index e958468cd5861..344aaf18f858f 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -385,7 +385,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. var storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); - var storageHandle = _storageService.GetHandle(storageIdentifier); + var storageHandle = TemporaryStorageService.GetHandle(storageIdentifier); return ReadModuleMetadataFromStorage(storageHandle); } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index c349cadcd41e4..e09ba20e21cf2 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -124,7 +124,7 @@ public async Task WriteToTemporaryStorageAsync(Sourc private TemporaryStorageTextHandle CreateHandleFromStorage(TemporaryTextStorage storage) { var identifier = new TemporaryStorageTextIdentifier( - storage.Name, storage.Offset, storage.Size, storage.ChecksumAlgorithm, storage.Encoding); + storage.Name, storage.Offset, storage.Size, storage.ChecksumAlgorithm, storage.Encoding, storage.ContentHash); return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); } @@ -146,6 +146,12 @@ internal static TemporaryStorageStreamHandle GetHandle(TemporaryStorageIdentifie return new(memoryMappedFile, storageIdentifier); } + internal TemporaryStorageTextHandle GetHandle(TemporaryStorageTextIdentifier storageIdentifier) + { + var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); + return new(this, memoryMappedFile, storageIdentifier); + } + /// /// Allocate shared storage of a specified size. /// @@ -251,7 +257,7 @@ private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedM #endif } - public sealed class TemporaryTextStorage // : ITemporaryTextStorageInternal, ITemporaryStorageWithName + private sealed class TemporaryTextStorage // : ITemporaryTextStorageInternal, ITemporaryStorageWithName { private readonly TemporaryStorageService _service; private SourceHashAlgorithm _checksumAlgorithm; diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index 8e4dde76b2938..e19dbf62c9fc2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -29,14 +29,3 @@ public interface ITemporaryStreamStorage : IDisposable void WriteStream(Stream stream, CancellationToken cancellationToken = default); Task WriteStreamAsync(Stream stream, CancellationToken cancellationToken = default); } - -/// -/// TemporaryStorage can be used to read and write text to a temporary storage location. -/// -//internal interface ITemporaryTextStorageInternal -//{ -// SourceText ReadText(CancellationToken cancellationToken = default); -// Task ReadTextAsync(CancellationToken cancellationToken = default); -// void WriteText(SourceText text, CancellationToken cancellationToken = default); -// Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); -//} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs index 2d432b715b2b2..418a8a7fe50cf 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageStreamHandle.cs @@ -3,11 +3,7 @@ // See the LICENSE file in the project root for more information. using System.IO; -using System.Text; using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Host; @@ -28,32 +24,3 @@ internal interface ITemporaryStorageStreamHandle /// Stream ReadFromTemporaryStorage(CancellationToken cancellationToken); } - -internal interface ITemporaryStorageTextHandle -{ - public TemporaryStorageTextIdentifier Identifier { get; } - - SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); - Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); -} - -internal sealed record TemporaryStorageTextIdentifier( - string Name, long Offset, long Size, SourceHashAlgorithm ChecksumAlgorithm, Encoding? Encoding) -{ - public static TemporaryStorageTextIdentifier ReadFrom(ObjectReader reader) - => new( - reader.ReadRequiredString(), - reader.ReadInt64(), - reader.ReadInt64(), - (SourceHashAlgorithm)reader.ReadInt32(), - reader.ReadEncoding()); - - public void WriteTo(ObjectWriter writer) - { - writer.WriteString(Name); - writer.WriteInt64(Offset); - writer.WriteInt64(Size); - writer.WriteInt32((int)ChecksumAlgorithm); - writer.WriteEncoding(Encoding); - } -} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs new file mode 100644 index 0000000000000..eb0fc241b1417 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Host; + +internal interface ITemporaryStorageTextHandle +{ + public TemporaryStorageTextIdentifier Identifier { get; } + + SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); + Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs new file mode 100644 index 0000000000000..26489a52b21bc --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Host; + +/// +/// Identifier for a placed in a segment of temporary storage (generally a memory mapped file). +/// Can be used to identify that segment across processes, allowing for efficient sharing of data. +/// +internal sealed record TemporaryStorageTextIdentifier( + string Name, + long Offset, + long Size, + SourceHashAlgorithm ChecksumAlgorithm, + Encoding? Encoding, + ImmutableArray ContentHash) +{ + public static TemporaryStorageTextIdentifier ReadFrom(ObjectReader reader) + => new( + reader.ReadRequiredString(), + reader.ReadInt64(), + reader.ReadInt64(), + (SourceHashAlgorithm)reader.ReadInt32(), + reader.ReadEncoding(), + ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray())); + + public void WriteTo(ObjectWriter writer) + { + writer.WriteString(Name); + writer.WriteInt64(Offset); + writer.WriteInt64(Size); + writer.WriteInt32((int)ChecksumAlgorithm); + writer.WriteEncoding(Encoding); + writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(ContentHash)!); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index b342ad6bbcce0..c9b070337e0a6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -23,7 +23,8 @@ public ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, Canc { var storage = new TextStorage(); storage.WriteText(text); - var identifier = new TemporaryStorageTextIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length, text.ChecksumAlgorithm, text.Encoding); + var identifier = new TemporaryStorageTextIdentifier( + Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length, text.ChecksumAlgorithm, text.Encoding, text.GetContentHash()); var handle = new TrivialStorageTextHandle(identifier, storage); return handle; } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs index b4b327a618e44..7314888e4c9ba 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs @@ -48,8 +48,8 @@ private bool TryGetInitialSourceOrRecoverableText([NotNullWhen(true)] out ITextA public TextLoader? TextLoader => (_initialSourceOrRecoverableText as ITextAndVersionSource)?.TextLoader; - public ITemporaryTextStorageInternal? Storage - => (_initialSourceOrRecoverableText as RecoverableText)?.Storage; + public ITemporaryStorageTextHandle? StorageHandle + => (_initialSourceOrRecoverableText as RecoverableText)?.StorageHandle; public bool TryGetValue(LoadTextOptions options, [MaybeNullWhen(false)] out TextAndVersion value) { @@ -143,7 +143,7 @@ private sealed partial class RecoverableText public readonly ITextAndVersionSource? InitialSource; public readonly LoadTextOptions LoadTextOptions; - public ITemporaryTextStorageInternal? _storage; + public ITemporaryStorageTextHandle? _storageHandle; public RecoverableText(ITextAndVersionSource source, TextAndVersion textAndVersion, LoadTextOptions options, SolutionServices services) { @@ -166,37 +166,36 @@ public RecoverableText(ITextAndVersionSource source, TextAndVersion textAndVersi public TextAndVersion ToTextAndVersion(SourceText text) => TextAndVersion.Create(text, Version, LoadDiagnostic); - public ITemporaryTextStorageInternal? Storage => _storage; + public ITemporaryStorageTextHandle? StorageHandle => _storageHandle; private async Task RecoverAsync(CancellationToken cancellationToken) { - Contract.ThrowIfNull(_storage); + Contract.ThrowIfNull(_storageHandle); using (Logger.LogBlock(FunctionId.Workspace_Recoverable_RecoverTextAsync, cancellationToken)) { - return await _storage.ReadTextAsync(cancellationToken).ConfigureAwait(false); + return await _storageHandle.ReadFromTemporaryStorageAsync(cancellationToken).ConfigureAwait(false); } } private SourceText Recover(CancellationToken cancellationToken) { - Contract.ThrowIfNull(_storage); + Contract.ThrowIfNull(_storageHandle); using (Logger.LogBlock(FunctionId.Workspace_Recoverable_RecoverText, cancellationToken)) { - return _storage.ReadText(cancellationToken); + return _storageHandle.ReadFromTemporaryStorage(cancellationToken); } } private async Task SaveAsync(SourceText text, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(_storage == null); // Cannot save more than once + Contract.ThrowIfFalse(_storageHandle == null); // Cannot save more than once - var storage = _storageService.CreateTemporaryTextStorage(); - await storage.WriteTextAsync(text, cancellationToken).ConfigureAwait(false); + var handle = await _storageService.WriteToTemporaryStorageAsync(text, cancellationToken).ConfigureAwait(false); - // make sure write is done before setting _storage field - Interlocked.CompareExchange(ref _storage, storage, null); + // make sure write is done before setting _storageHandle field + Interlocked.CompareExchange(ref _storageHandle, handle, null); // Only set _initialValue to null once writing to the storage service completes fully. If the save did not // complete, we want to keep it around to service future requests. Once we do clear out this value, then diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 53c6008610ccc..63fa8cb9f446d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -82,8 +82,8 @@ private static ITextAndVersionSource CreateRecoverableText(TextAndVersion text, : new RecoverableTextAndVersion(new ConstantTextAndVersionSource(text), services); } - public ITemporaryTextStorageInternal? Storage - => (TextAndVersionSource as RecoverableTextAndVersion)?.Storage; + public ITemporaryStorageTextHandle? StorageHandle + => (TextAndVersionSource as RecoverableTextAndVersion)?.StorageHandle; public bool TryGetText([NotNullWhen(returnValue: true)] out SourceText? text) { diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs index 2bd1e0f417690..053cad8235405 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/TemporaryStorageServiceTests.cs @@ -73,41 +73,17 @@ public void TestTemporaryStorageStream() private static void TestTemporaryStorage(ITemporaryStorageServiceInternal temporaryStorageService, SourceText text) { - // create a temporary storage location - var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(); - // write text into it - temporaryStorage.WriteTextAsync(text).Wait(); + var handle = temporaryStorageService.WriteToTemporaryStorage(text, CancellationToken.None); // read text back from it - var text2 = temporaryStorage.ReadTextAsync().Result; + var text2 = handle.ReadFromTemporaryStorage(CancellationToken.None); Assert.NotSame(text, text2); Assert.Equal(text.ToString(), text2.ToString()); Assert.Equal(text.Encoding, text2.Encoding); } - [ConditionalFact(typeof(WindowsOnly))] - public void TestTemporaryTextStorageExceptions() - { - using var workspace = new AdhocWorkspace(); - var textFactory = Assert.IsType(workspace.Services.GetService()); - var service = Assert.IsType(workspace.Services.GetRequiredService()); - var storage = service.CreateTemporaryTextStorage(); - - // Nothing has been written yet - Assert.Throws(() => storage.ReadText()); - Assert.Throws(() => storage.ReadTextAsync().Result); - - // write a normal string - var text = SourceText.From(new string(' ', 4096) + "public class A {}"); - storage.WriteTextAsync(text).Wait(); - - // Writing multiple times is not allowed - Assert.Throws(() => storage.WriteText(text)); - Assert.Throws(() => storage.WriteTextAsync(text).Wait()); - } - [ConditionalFact(typeof(WindowsOnly))] public void TestZeroLengthStreams() { From fe6632516251cfc73cdcc5e59424e666469ec535 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 11:38:16 -0700 Subject: [PATCH 258/292] refactory --- .../Serialization/SerializableSourceText.cs | 15 +++- .../SerializerService_Reference.cs | 2 +- .../TemporaryStorageService.cs | 29 ++++--- .../ITemporaryStorageTextHandle.cs | 2 +- .../TemporaryStorageTextIdentifier.cs | 78 +++++++++---------- ...StorageService.TrivialStorageTextHandle.cs | 4 +- .../TrivialTemporaryStorageService.cs | 3 +- 7 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs index c40bfa5cbc108..06e2b900e3035 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializableSourceText.cs @@ -57,7 +57,7 @@ internal sealed class SerializableSourceText public readonly Checksum ContentChecksum; public SerializableSourceText(TemporaryStorageTextHandle storageHandle) - : this(storageHandle, text: null, storageHandle.Identifier.ContentHash) + : this(storageHandle, text: null, storageHandle.ContentHash) { } @@ -75,7 +75,7 @@ private SerializableSourceText(TemporaryStorageTextHandle? storageHandle, Source ContentChecksum = Checksum.Create(contentHash); #if DEBUG - var computedContentHash = TryGetText()?.GetContentHash() ?? _storageHandle!.Identifier.ContentHash; + var computedContentHash = TryGetText()?.GetContentHash() ?? _storageHandle!.ContentHash; Debug.Assert(contentHash.SequenceEqual(computedContentHash)); #endif } @@ -142,10 +142,14 @@ public static ValueTask FromTextDocumentStateAsync( public void Serialize(ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + if (_storageHandle is not null) { writer.WriteInt32((int)SerializationKinds.MemoryMapFile); _storageHandle.Identifier.WriteTo(writer); + writer.WriteInt32((int)_storageHandle.ChecksumAlgorithm); + writer.WriteEncoding(_storageHandle.Encoding); + writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(_storageHandle.ContentHash)!); } else { @@ -173,8 +177,11 @@ public static SerializableSourceText Deserialize( if (kind == SerializationKinds.MemoryMapFile) { - var identifier = TemporaryStorageTextIdentifier.ReadFrom(reader); - var storageHandle = storageService.GetHandle(identifier); + var identifier = TemporaryStorageIdentifier.ReadFrom(reader); + var checksumAlgorithm = (SourceHashAlgorithm)reader.ReadInt32(); + var encoding = reader.ReadEncoding(); + var contentHash = ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray()); + var storageHandle = storageService.GetTextHandle(identifier, checksumAlgorithm, encoding, contentHash); return new SerializableSourceText(storageHandle); } diff --git a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs index 344aaf18f858f..321876a3d0626 100644 --- a/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs +++ b/src/Workspaces/Core/Portable/Serialization/SerializerService_Reference.cs @@ -385,7 +385,7 @@ private static bool TryWritePortableExecutableReferenceBackedByTemporaryStorageT // Host passed us a segment of its own memory mapped file. We can just refer to that segment directly as it // will not be released by the host. var storageIdentifier = TemporaryStorageIdentifier.ReadFrom(reader); - var storageHandle = TemporaryStorageService.GetHandle(storageIdentifier); + var storageHandle = TemporaryStorageService.GetStreamHandle(storageIdentifier); return ReadModuleMetadataFromStorage(storageHandle); } diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index e09ba20e21cf2..a98f3097b6962 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -123,9 +123,8 @@ public async Task WriteToTemporaryStorageAsync(Sourc private TemporaryStorageTextHandle CreateHandleFromStorage(TemporaryTextStorage storage) { - var identifier = new TemporaryStorageTextIdentifier( - storage.Name, storage.Offset, storage.Size, storage.ChecksumAlgorithm, storage.Encoding, storage.ContentHash); - return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier); + var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); + return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier, storage.ChecksumAlgorithm, storage.Encoding, storage.ContentHash); } ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) @@ -140,16 +139,20 @@ public TemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, Cance return new(storage.MemoryMappedInfo.MemoryMappedFile, identifier); } - internal static TemporaryStorageStreamHandle GetHandle(TemporaryStorageIdentifier storageIdentifier) + internal static TemporaryStorageStreamHandle GetStreamHandle(TemporaryStorageIdentifier storageIdentifier) { var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); return new(memoryMappedFile, storageIdentifier); } - internal TemporaryStorageTextHandle GetHandle(TemporaryStorageTextIdentifier storageIdentifier) + internal TemporaryStorageTextHandle GetTextHandle( + TemporaryStorageIdentifier storageIdentifier, + SourceHashAlgorithm checksumAlgorithm, + Encoding? encoding, + ImmutableArray contentHash) { var memoryMappedFile = MemoryMappedFile.OpenExisting(storageIdentifier.Name); - return new(this, memoryMappedFile, storageIdentifier); + return new(this, memoryMappedFile, storageIdentifier, checksumAlgorithm, encoding, contentHash); } /// @@ -200,10 +203,16 @@ public static string CreateUniqueName(long size) public sealed class TemporaryStorageTextHandle( TemporaryStorageService storageService, MemoryMappedFile memoryMappedFile, - TemporaryStorageTextIdentifier identifier) + TemporaryStorageIdentifier identifier, + SourceHashAlgorithm checksumAlgorithm, + Encoding? encoding, + ImmutableArray contentHash) : ITemporaryStorageTextHandle { - public TemporaryStorageTextIdentifier Identifier => identifier; + public TemporaryStorageIdentifier Identifier => identifier; + public SourceHashAlgorithm ChecksumAlgorithm => checksumAlgorithm; + public Encoding? Encoding => encoding; + public ImmutableArray ContentHash => contentHash; public async Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken) { @@ -235,11 +244,11 @@ public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) using var reader = CreateTextReaderFromTemporaryStorage(stream); // we pass in encoding we got from original source text even if it is null. - return storageService._textFactory.CreateText(reader, Identifier.Encoding, Identifier.ChecksumAlgorithm, cancellationToken); + return storageService._textFactory.CreateText(reader, encoding, checksumAlgorithm, cancellationToken); } } - private static unsafe TextReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) + private static unsafe DirectMemoryAccessStreamReader CreateTextReaderFromTemporaryStorage(UnmanagedMemoryStream stream) { var src = (char*)stream.PositionPointer; diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs index eb0fc241b1417..d2c1dcccc88b1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageTextHandle.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Host; internal interface ITemporaryStorageTextHandle { - public TemporaryStorageTextIdentifier Identifier { get; } + public TemporaryStorageIdentifier Identifier { get; } SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken); Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs index 26489a52b21bc..d9915b335867c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs @@ -1,43 +1,43 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. +//// See the LICENSE file in the project root for more information. -using System.Collections.Immutable; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; +//using System.Collections.Immutable; +//using System.Runtime.InteropServices; +//using System.Text; +//using Microsoft.CodeAnalysis.Text; +//using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Host; +//namespace Microsoft.CodeAnalysis.Host; -/// -/// Identifier for a placed in a segment of temporary storage (generally a memory mapped file). -/// Can be used to identify that segment across processes, allowing for efficient sharing of data. -/// -internal sealed record TemporaryStorageTextIdentifier( - string Name, - long Offset, - long Size, - SourceHashAlgorithm ChecksumAlgorithm, - Encoding? Encoding, - ImmutableArray ContentHash) -{ - public static TemporaryStorageTextIdentifier ReadFrom(ObjectReader reader) - => new( - reader.ReadRequiredString(), - reader.ReadInt64(), - reader.ReadInt64(), - (SourceHashAlgorithm)reader.ReadInt32(), - reader.ReadEncoding(), - ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray())); +///// +///// Identifier for a placed in a segment of temporary storage (generally a memory mapped file). +///// Can be used to identify that segment across processes, allowing for efficient sharing of data. +///// +//internal sealed record TemporaryStorageTextIdentifier( +// string Name, +// long Offset, +// long Size, +// SourceHashAlgorithm ChecksumAlgorithm, +// Encoding? Encoding, +// ImmutableArray ContentHash) +//{ +// public static TemporaryStorageTextIdentifier ReadFrom(ObjectReader reader) +// => new( +// reader.ReadRequiredString(), +// reader.ReadInt64(), +// reader.ReadInt64(), +// (SourceHashAlgorithm)reader.ReadInt32(), +// reader.ReadEncoding(), +// ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray())); - public void WriteTo(ObjectWriter writer) - { - writer.WriteString(Name); - writer.WriteInt64(Offset); - writer.WriteInt64(Size); - writer.WriteInt32((int)ChecksumAlgorithm); - writer.WriteEncoding(Encoding); - writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(ContentHash)!); - } -} +// public void WriteTo(ObjectWriter writer) +// { +// writer.WriteString(Name); +// writer.WriteInt64(Offset); +// writer.WriteInt64(Size); +// writer.WriteInt32((int)ChecksumAlgorithm); +// writer.WriteEncoding(Encoding); +// writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(ContentHash)!); +// } +//} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs index c53bf7dff6813..9bef5f0738b8c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs @@ -12,10 +12,10 @@ namespace Microsoft.CodeAnalysis; internal sealed partial class TrivialTemporaryStorageService { private sealed class TrivialStorageTextHandle( - TemporaryStorageTextIdentifier identifier, + TemporaryStorageIdentifier identifier, TextStorage storage) : ITemporaryStorageTextHandle { - public TemporaryStorageTextIdentifier Identifier => identifier; + public TemporaryStorageIdentifier Identifier => identifier; public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) => storage.ReadText(); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index c9b070337e0a6..113636d000d9d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -23,8 +23,7 @@ public ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, Canc { var storage = new TextStorage(); storage.WriteText(text); - var identifier = new TemporaryStorageTextIdentifier( - Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length, text.ChecksumAlgorithm, text.Encoding, text.GetContentHash()); + var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length); var handle = new TrivialStorageTextHandle(identifier, storage); return handle; } From bc51702c5f1f5f0af49ce49c779ba15dcd59139b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 11:38:49 -0700 Subject: [PATCH 259/292] Simplify --- .../TemporaryStorageService.cs | 15 +------ .../ITemporaryStorageWithName.cs | 29 ------------- .../TemporaryStorageTextIdentifier.cs | 43 ------------------- 3 files changed, 1 insertion(+), 86 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs delete mode 100644 src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index a98f3097b6962..519d953124415 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -94,13 +94,6 @@ private TemporaryStorageService(IWorkspaceThreadingService? workspaceThreadingSe _textFactory = textFactory; } - //public ITemporaryTextStorageInternal CreateTemporaryTextStorage() - // => new TemporaryTextStorage(this); - - //public TemporaryTextStorage AttachTemporaryTextStorage( - // string storageName, long offset, long size, SourceHashAlgorithm checksumAlgorithm, Encoding? encoding, ImmutableArray contentHash) - // => new(this, storageName, offset, size, checksumAlgorithm, encoding, contentHash); - ITemporaryStorageTextHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) => WriteToTemporaryStorage(text, cancellationToken); @@ -258,15 +251,9 @@ private static unsafe DirectMemoryAccessStreamReader CreateTextReaderFromTempora return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); } - -#if false - var storage = new TemporaryStreamStorage( - storageService, memoryMappedFile, this.Identifier.Name, this.Identifier.Offset, this.Identifier.Size); - return storage.ReadStream(cancellationToken); -#endif } - private sealed class TemporaryTextStorage // : ITemporaryTextStorageInternal, ITemporaryStorageWithName + private sealed class TemporaryTextStorage { private readonly TemporaryStorageService _service; private SourceHashAlgorithm _checksumAlgorithm; diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs deleted file mode 100644 index ab3f73b119383..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageWithName.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.CodeAnalysis.Host; - -///// -///// TemporaryStorage can be used to read and write text to a temporary storage location. -///// -//internal interface ITemporaryStorageWithName -//{ -// // TODO: clean up https://github.com/dotnet/roslyn/issues/43037 -// // Name shouldn't be nullable. - -// /// -// /// Get name of the temporary storage -// /// -// string? Name { get; } - -// /// -// /// Get offset of the temporary storage -// /// -// long Offset { get; } - -// /// -// /// Get size of the temporary storage -// /// -// long Size { get; } -//} diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs deleted file mode 100644 index d9915b335867c..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TemporaryStorageTextIdentifier.cs +++ /dev/null @@ -1,43 +0,0 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. -//// See the LICENSE file in the project root for more information. - -//using System.Collections.Immutable; -//using System.Runtime.InteropServices; -//using System.Text; -//using Microsoft.CodeAnalysis.Text; -//using Roslyn.Utilities; - -//namespace Microsoft.CodeAnalysis.Host; - -///// -///// Identifier for a placed in a segment of temporary storage (generally a memory mapped file). -///// Can be used to identify that segment across processes, allowing for efficient sharing of data. -///// -//internal sealed record TemporaryStorageTextIdentifier( -// string Name, -// long Offset, -// long Size, -// SourceHashAlgorithm ChecksumAlgorithm, -// Encoding? Encoding, -// ImmutableArray ContentHash) -//{ -// public static TemporaryStorageTextIdentifier ReadFrom(ObjectReader reader) -// => new( -// reader.ReadRequiredString(), -// reader.ReadInt64(), -// reader.ReadInt64(), -// (SourceHashAlgorithm)reader.ReadInt32(), -// reader.ReadEncoding(), -// ImmutableCollectionsMarshal.AsImmutableArray(reader.ReadByteArray())); - -// public void WriteTo(ObjectWriter writer) -// { -// writer.WriteString(Name); -// writer.WriteInt64(Offset); -// writer.WriteInt64(Size); -// writer.WriteInt32((int)ChecksumAlgorithm); -// writer.WriteEncoding(Encoding); -// writer.WriteByteArray(ImmutableCollectionsMarshal.AsArray(ContentHash)!); -// } -//} From c8badaa6446539a00d69dcb5b7cbb87b806ab13e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 11:40:58 -0700 Subject: [PATCH 260/292] Delete helper type --- .../TemporaryStorageService.cs | 76 +++++++------------ 1 file changed, 26 insertions(+), 50 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index 519d953124415..be62b7fe34a0a 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -126,10 +126,32 @@ ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryS public TemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; - var storage = new TemporaryStreamStorage(this); - storage.WriteStream(stream, cancellationToken); - var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); - return new(storage.MemoryMappedInfo.MemoryMappedFile, identifier); + var memoryMappedInfo = WriteStreamToMemoryMappedFile(); + var identifier = new TemporaryStorageIdentifier(memoryMappedInfo.Name, memoryMappedInfo.Offset, memoryMappedInfo.Size); + return new(memoryMappedInfo.MemoryMappedFile, identifier); + + MemoryMappedInfo WriteStreamToMemoryMappedFile() + { + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) + { + var size = stream.Length; + var memoryMappedInfo = this.CreateTemporaryStorage(size); + using var viewStream = memoryMappedInfo.CreateWritableStream(); + + using var pooledObject = SharedPools.ByteArray.GetPooledObject(); + var buffer = pooledObject.Object; + while (true) + { + var count = stream.Read(buffer, 0, buffer.Length); + if (count == 0) + break; + + viewStream.Write(buffer, 0, count); + } + + return memoryMappedInfo; + } + } } internal static TemporaryStorageStreamHandle GetStreamHandle(TemporaryStorageIdentifier storageIdentifier) @@ -341,50 +363,4 @@ public async Task WriteTextAsync(SourceText text, CancellationToken cancellation WriteText(text, cancellationToken); } } - - internal sealed class TemporaryStreamStorage - { - private readonly TemporaryStorageService _service; - private MemoryMappedInfo? _memoryMappedInfo; - - public TemporaryStreamStorage(TemporaryStorageService service) - => _service = service; - - public TemporaryStreamStorage( - TemporaryStorageService service, MemoryMappedFile file, string storageName, long offset, long size) - { - _service = service; - _memoryMappedInfo = new MemoryMappedInfo(file, storageName, offset, size); - } - - public MemoryMappedInfo MemoryMappedInfo => _memoryMappedInfo ?? throw new InvalidOperationException(); - - public string Name => this.MemoryMappedInfo.Name; - public long Offset => this.MemoryMappedInfo.Offset; - public long Size => this.MemoryMappedInfo.Size; - - public void WriteStream(Stream stream, CancellationToken cancellationToken) - { - if (_memoryMappedInfo != null) - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) - { - var size = stream.Length; - _memoryMappedInfo = _service.CreateTemporaryStorage(size); - using var viewStream = _memoryMappedInfo.CreateWritableStream(); - - using var pooledObject = SharedPools.ByteArray.GetPooledObject(); - var buffer = pooledObject.Object; - while (true) - { - var count = stream.Read(buffer, 0, buffer.Length); - if (count == 0) - break; - - viewStream.Write(buffer, 0, count); - } - } - } - } } From 1b9cc7ce2a74c9a6825717e1a2dff8ae21786555 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 11:46:00 -0700 Subject: [PATCH 261/292] Delete helper type --- .../TemporaryStorageService.cs | 152 +++++------------- 1 file changed, 40 insertions(+), 112 deletions(-) diff --git a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs index be62b7fe34a0a..7a8431f550016 100644 --- a/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/TemporaryStorage/TemporaryStorageService.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.IO; using System.IO.MemoryMappedFiles; -using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; @@ -102,22 +101,39 @@ async Task ITemporaryStorageServiceInternal.WriteTo public TemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) { - var storage = new TemporaryTextStorage(this); - storage.WriteText(text, cancellationToken); - return CreateHandleFromStorage(storage); + var memoryMappedInfo = WriteToMemoryMappedFile(); + var identifier = new TemporaryStorageIdentifier(memoryMappedInfo.Name, memoryMappedInfo.Offset, memoryMappedInfo.Size); + return new(this, memoryMappedInfo.MemoryMappedFile, identifier, text.ChecksumAlgorithm, text.Encoding, text.GetContentHash()); + + MemoryMappedInfo WriteToMemoryMappedFile() + { + using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteText, cancellationToken)) + { + // the method we use to get text out of SourceText uses Unicode (2bytes per char). + var size = Encoding.Unicode.GetMaxByteCount(text.Length); + var memoryMappedInfo = this.CreateTemporaryStorage(size); + + // Write the source text out as Unicode. We expect that to be cheap. + using var stream = memoryMappedInfo.CreateWritableStream(); + { + using var writer = new StreamWriter(stream, Encoding.Unicode); + text.Write(writer, cancellationToken); + } + + return memoryMappedInfo; + } + } } public async Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken) { - var storage = new TemporaryTextStorage(this); - await storage.WriteTextAsync(text, cancellationToken).ConfigureAwait(false); - return CreateHandleFromStorage(storage); - } + if (this._workspaceThreadingService is { IsOnMainThread: true }) + { + await Task.Yield().ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } - private TemporaryStorageTextHandle CreateHandleFromStorage(TemporaryTextStorage storage) - { - var identifier = new TemporaryStorageIdentifier(storage.Name, storage.Offset, storage.Size); - return new(this, storage.MemoryMappedInfo.MemoryMappedFile, identifier, storage.ChecksumAlgorithm, storage.Encoding, storage.ContentHash); + return WriteToTemporaryStorage(text, cancellationToken); } ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) @@ -126,27 +142,28 @@ ITemporaryStorageStreamHandle ITemporaryStorageServiceInternal.WriteToTemporaryS public TemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { stream.Position = 0; - var memoryMappedInfo = WriteStreamToMemoryMappedFile(); + var memoryMappedInfo = WriteToMemoryMappedFile(); var identifier = new TemporaryStorageIdentifier(memoryMappedInfo.Name, memoryMappedInfo.Offset, memoryMappedInfo.Size); return new(memoryMappedInfo.MemoryMappedFile, identifier); - MemoryMappedInfo WriteStreamToMemoryMappedFile() + MemoryMappedInfo WriteToMemoryMappedFile() { using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteStream, cancellationToken)) { var size = stream.Length; var memoryMappedInfo = this.CreateTemporaryStorage(size); using var viewStream = memoryMappedInfo.CreateWritableStream(); - - using var pooledObject = SharedPools.ByteArray.GetPooledObject(); - var buffer = pooledObject.Object; - while (true) { - var count = stream.Read(buffer, 0, buffer.Length); - if (count == 0) - break; - - viewStream.Write(buffer, 0, count); + using var pooledObject = SharedPools.ByteArray.GetPooledObject(); + var buffer = pooledObject.Object; + while (true) + { + var count = stream.Read(buffer, 0, buffer.Length); + if (count == 0) + break; + + viewStream.Write(buffer, 0, count); + } } return memoryMappedInfo; @@ -274,93 +291,4 @@ private static unsafe DirectMemoryAccessStreamReader CreateTextReaderFromTempora return new DirectMemoryAccessStreamReader(src + 1, (int)stream.Length / sizeof(char) - 1); } } - - private sealed class TemporaryTextStorage - { - private readonly TemporaryStorageService _service; - private SourceHashAlgorithm _checksumAlgorithm; - private Encoding? _encoding; - private ImmutableArray _contentHash; - private MemoryMappedInfo? _memoryMappedInfo; - - public MemoryMappedInfo MemoryMappedInfo => _memoryMappedInfo ?? throw new InvalidOperationException(); - - public TemporaryTextStorage(TemporaryStorageService service) - => _service = service; - - public TemporaryTextStorage( - TemporaryStorageService service, - string storageName, - long offset, - long size, - SourceHashAlgorithm checksumAlgorithm, - Encoding? encoding, - ImmutableArray contentHash) - { - _service = service; - _checksumAlgorithm = checksumAlgorithm; - _encoding = encoding; - _contentHash = contentHash; - _memoryMappedInfo = MemoryMappedInfo.OpenExisting(storageName, offset, size); - } - - public string Name => this.MemoryMappedInfo.Name; - public long Offset => this.MemoryMappedInfo.Offset; - public long Size => this.MemoryMappedInfo.Size; - - /// - /// Gets the value for the property for the - /// represented by this temporary storage. - /// - public SourceHashAlgorithm ChecksumAlgorithm => _checksumAlgorithm; - - /// - /// Gets the value for the property for the - /// represented by this temporary storage. - /// - public Encoding? Encoding => _encoding; - - /// - /// Gets the checksum for the represented by this temporary storage. This is equivalent - /// to calling . - /// - public ImmutableArray ContentHash => _contentHash; - - public void WriteText(SourceText text, CancellationToken cancellationToken) - { - if (_memoryMappedInfo != null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - - using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_WriteText, cancellationToken)) - { - _checksumAlgorithm = text.ChecksumAlgorithm; - _encoding = text.Encoding; - _contentHash = text.GetContentHash(); - - // the method we use to get text out of SourceText uses Unicode (2bytes per char). - var size = Encoding.Unicode.GetMaxByteCount(text.Length); - _memoryMappedInfo = _service.CreateTemporaryStorage(size); - - // Write the source text out as Unicode. We expect that to be cheap. - using var stream = _memoryMappedInfo.CreateWritableStream(); - using var writer = new StreamWriter(stream, Encoding.Unicode); - - text.Write(writer, cancellationToken); - } - } - - public async Task WriteTextAsync(SourceText text, CancellationToken cancellationToken) - { - // See commentary in ReadTextAsync for why this is implemented this way. - if (_service._workspaceThreadingService is { IsOnMainThread: true }) - { - await Task.Yield().ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - } - - WriteText(text, cancellationToken); - } - } } From e4a2ecc3c215932244640eb23cc81c6b89e7979a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 11:51:53 -0700 Subject: [PATCH 262/292] Simplify --- ...StorageService.TrivialStorageTextHandle.cs | 4 +-- .../TrivialTemporaryStorageService.cs | 25 +------------------ 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs index 9bef5f0738b8c..fb09daa83f991 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageTextHandle.cs @@ -13,12 +13,12 @@ internal sealed partial class TrivialTemporaryStorageService { private sealed class TrivialStorageTextHandle( TemporaryStorageIdentifier identifier, - TextStorage storage) : ITemporaryStorageTextHandle + SourceText sourceText) : ITemporaryStorageTextHandle { public TemporaryStorageIdentifier Identifier => identifier; public SourceText ReadFromTemporaryStorage(CancellationToken cancellationToken) - => storage.ReadText(); + => sourceText; public Task ReadFromTemporaryStorageAsync(CancellationToken cancellationToken) => Task.FromResult(ReadFromTemporaryStorage(cancellationToken)); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 113636d000d9d..8f1e88f9ac3e1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -21,10 +21,8 @@ private TrivialTemporaryStorageService() public ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken) { - var storage = new TextStorage(); - storage.WriteText(text); var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: text.Length); - var handle = new TrivialStorageTextHandle(identifier, storage); + var handle = new TrivialStorageTextHandle(identifier, text); return handle; } @@ -65,25 +63,4 @@ public void WriteStream(Stream stream) } } } - - private sealed class TextStorage - { - private SourceText? _sourceText; - - public SourceText ReadText() - => _sourceText ?? throw new InvalidOperationException(); - - public Task ReadTextAsync() - => Task.FromResult(ReadText()); - - public void WriteText(SourceText text) - { - // This is a trivial implementation, indeed. Note, however, that we retain a strong - // reference to the source text, which defeats the intent of RecoverableTextAndVersion, but - // is appropriate for this trivial implementation. - var existingValue = Interlocked.CompareExchange(ref _sourceText, text, null); - if (existingValue is not null) - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - } } From 61e33a1335c6a23c5e8938dd5eedff052aa339f4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 12:00:24 -0700 Subject: [PATCH 263/292] Simplify --- ...orageService.TrivialStorageStreamHandle.cs | 8 +++-- .../TrivialTemporaryStorageService.cs | 34 +++---------------- 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs index 2b750b62a5807..d3cb02f201d39 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.TrivialStorageStreamHandle.cs @@ -12,11 +12,15 @@ internal sealed partial class TrivialTemporaryStorageService { private sealed class TrivialStorageStreamHandle( TemporaryStorageIdentifier storageIdentifier, - StreamStorage streamStorage) : ITemporaryStorageStreamHandle + MemoryStream streamCopy) : ITemporaryStorageStreamHandle { public TemporaryStorageIdentifier Identifier => storageIdentifier; public Stream ReadFromTemporaryStorage(CancellationToken cancellationToken) - => streamStorage.ReadStream(); + { + // Return a read-only view of the underlying buffer to prevent users from overwriting or directly + // disposing the backing storage. + return new MemoryStream(streamCopy.GetBuffer(), 0, (int)streamCopy.Length, writable: false); + } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs index 8f1e88f9ac3e1..a9304b70e07a6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/TrivialTemporaryStorageService.cs @@ -31,36 +31,12 @@ public Task WriteToTemporaryStorageAsync(SourceText public ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken) { - stream.Position = 0; - var storage = new StreamStorage(); - storage.WriteStream(stream); + var newStream = new MemoryStream(); + stream.CopyTo(newStream); + newStream.Position = 0; + var identifier = new TemporaryStorageIdentifier(Guid.NewGuid().ToString("N"), Offset: 0, Size: stream.Length); - var handle = new TrivialStorageStreamHandle(identifier, storage); + var handle = new TrivialStorageStreamHandle(identifier, newStream); return handle; } - - private sealed class StreamStorage - { - private MemoryStream? _stream; - - public Stream ReadStream() - { - var stream = _stream ?? throw new InvalidOperationException(); - - // Return a read-only view of the underlying buffer to prevent users from overwriting or directly - // disposing the backing storage. - return new MemoryStream(stream.GetBuffer(), 0, (int)stream.Length, writable: false); - } - - public void WriteStream(Stream stream) - { - var newStream = new MemoryStream(); - stream.CopyTo(newStream); - var existingValue = Interlocked.CompareExchange(ref _stream, newStream, null); - if (existingValue is not null) - { - throw new InvalidOperationException(WorkspacesResources.Temporary_storage_cannot_be_written_more_than_once); - } - } - } } From b61ffd19b9b719544da660fe4fab17a525c7acc9 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Wed, 24 Apr 2024 12:54:03 -0700 Subject: [PATCH 264/292] CR feedback --- ...AbstractDocumentDiagnosticSourceProvider.cs | 15 +++++++++++++-- .../AbstractDocumentPullDiagnosticHandler.cs | 2 +- ...tEditAndContinueDiagnosticSourceProvider.cs | 15 +++------------ ...yntaxAndSemanticDiagnosticSourceProvider.cs | 15 +++------------ .../DocumentTaskDiagnosticSourceProvider.cs | 18 +++--------------- ...DocumentNonLocalDiagnosticSourceProvider.cs | 14 +++++--------- 6 files changed, 28 insertions(+), 51 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs index 74984ee495ce4..7bcc29af01015 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentDiagnosticSourceProvider.cs @@ -8,10 +8,21 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal abstract class AbstractDocumentDiagnosticSourceProvider(string name) : IDiagnosticSourceProvider +internal abstract class AbstractDocumentDiagnosticSourceProvider(string name) : IDiagnosticSourceProvider where TDocument : TextDocument { public bool IsDocument => true; public string Name => name; - public abstract ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); + protected abstract IDiagnosticSource? CreateDiagnosticSource(TDocument document); + + public virtual ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } document && + CreateDiagnosticSource(document) is { } source) + { + return new([source]); + } + + return new([]); + } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index 95792e82c09c4..0c25c041b3e68 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -36,7 +36,7 @@ protected override ValueTask> GetOrderedDiagno return DiagnosticSourceManager.CreateDiagnosticSourcesAsync(context, requestDiagnosticCategory, isDocument: true, cancellationToken); } - protected static TextDocument? GetOpenDocument(RequestContext context) + private static TextDocument? GetOpenDocument(RequestContext context) { // Note: context.Document may be null in the case where the client is asking about a document that we have // since removed from the workspace. In this case, we don't really have anything to process. diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs index 76e37b53b4d2b..f60eb176238e0 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentEditAndContinueDiagnosticSourceProvider.cs @@ -3,10 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; using System.Composition; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; @@ -16,14 +13,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentEditAndContinueDiagnosticSourceProvider() - : AbstractDocumentDiagnosticSourceProvider(PullDiagnosticCategories.EditAndContinue) + : AbstractDocumentDiagnosticSourceProvider(PullDiagnosticCategories.EditAndContinue) { - public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - if (context.GetTrackedDocument() is not Document document) - return new([]); - - var source = EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document); - return new([source]); - } + protected override IDiagnosticSource? CreateDiagnosticSource(Document document) + => EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs index 5f83473c67c8e..8034579ba1feb 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -3,16 +3,13 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; using System.Composition; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal abstract class AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider : AbstractDocumentDiagnosticSourceProvider +internal abstract class AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider : AbstractDocumentDiagnosticSourceProvider { private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; private readonly DiagnosticKind _kind; @@ -24,14 +21,8 @@ public AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(IDiagnosticAnal _kind = kind; } - public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - if (context.TextDocument is null) - return new([]); - - var source = new DocumentDiagnosticSource(_diagnosticAnalyzerService, _kind, context.TextDocument); - return new([source]); - } + protected override IDiagnosticSource? CreateDiagnosticSource(TextDocument document) + => new DocumentDiagnosticSource(_diagnosticAnalyzerService, _kind, document); [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs index d721d1674e65d..e5c3c1a9f9c4a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentTaskDiagnosticSourceProvider.cs @@ -3,10 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; using System.Composition; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; @@ -16,18 +13,9 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) - : AbstractDocumentDiagnosticSourceProvider(PullDiagnosticCategories.Task) + : AbstractDocumentDiagnosticSourceProvider(PullDiagnosticCategories.Task) { - public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - if (context.TextDocument is not Document document) - { - context.TraceInformation("Ignoring task list diagnostics request because no document was provided"); - return new([]); - } - - var source = new TaskListDiagnosticSource(document, globalOptions); - return new([source]); - } + protected override IDiagnosticSource? CreateDiagnosticSource(Document document) + => new TaskListDiagnosticSource(document, globalOptions); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs index b8121d1aa09d5..0bbf4b0afbffc 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -3,10 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Immutable; using System.Composition; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; @@ -20,20 +17,19 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( [Import] IGlobalOptionService globalOptions, [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : AbstractDocumentDiagnosticSourceProvider(NonLocal) + : AbstractDocumentDiagnosticSourceProvider(NonLocal) { public const string NonLocal = "NonLocal_B69807DB-28FB-4846-884A-1152E54C8B62"; - public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + protected override IDiagnosticSource? CreateDiagnosticSource(TextDocument textDocument) { // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (context.TextDocument == null || - globalOptions.GetBackgroundAnalysisScope(context.TextDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) { - return new([]); + return null; } - return new([new NonLocalDocumentDiagnosticSource(context.TextDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer)]); + return new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, ShouldIncludeAnalyzer); // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer) => !analyzer.IsCompilerAnalyzer(); From 6b2f7d709bf3d2ddfbb661a9e6a8d0bb71efbb20 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Wed, 24 Apr 2024 12:57:15 -0700 Subject: [PATCH 265/292] More CR feedback --- .../Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index 8c8921a532a49..60eb7369d5785 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -74,8 +74,8 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.DiagnosticProvider ??= new(); // VS does not distinguish between document and workspace diagnostics, so we need to merge them. - var diagnosticSourceNames = _diagnosticSourceManager.GetSourceNames(true) - .Concat(_diagnosticSourceManager.GetSourceNames(false)) + var diagnosticSourceNames = _diagnosticSourceManager.GetSourceNames(isDocument: true) + .Concat(_diagnosticSourceManager.GetSourceNames(isDocument: false)) .Distinct(); serverCapabilities.DiagnosticProvider = serverCapabilities.DiagnosticProvider with { From 947c3f53b0a3c58e63825525a9b4e1e4b5d79d23 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Wed, 24 Apr 2024 14:20:26 -0700 Subject: [PATCH 266/292] More CR feedback --- .../IDiagnosticSourceManager.cs | 4 ++-- .../Internal/HotReloadDiagnosticManager.cs | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs index 8195cfbdfeb79..0c7eddd434578 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs @@ -16,7 +16,7 @@ internal interface IDiagnosticSourceManager /// /// Returns the names of all the sources that provide diagnostics for the given . /// - /// True for document sources and false for workspace sources. + /// for document sources and for workspace sources. ImmutableArray GetSourceNames(bool isDocument); /// @@ -31,7 +31,7 @@ internal interface IDiagnosticSourceManager /// Creates workspace diagnostic sources for the given . /// /// The context. - /// True for document sources and false for workspace sources. + /// Source name. /// The cancellation token. ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? sourceName, CancellationToken cancellationToken); } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs index 8ce6103c47a01..1f52edbb91297 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -17,6 +17,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class HotReloadDiagnosticManager([Import] IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager { + private readonly object syncLock = new(); private ImmutableArray _providers = ImmutableArray.Empty; ImmutableArray IHotReloadDiagnosticManager.Providers => _providers; void IHotReloadDiagnosticManager.RequestRefresh() => diagnosticsRefresher.RequestWorkspaceRefresh(); @@ -25,16 +26,22 @@ void IHotReloadDiagnosticManager.Register(IEnumerable providers) { - foreach (var provider in providers) - _providers = _providers.Remove(provider); + lock (syncLock) + { + foreach (var provider in providers) + _providers = _providers.Remove(provider); + } } } From d782b1509b1a79376815e0b587e4cf4d7c8c9e7a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 14:54:16 -0700 Subject: [PATCH 267/292] Fix issue where we could get assets back in a different order than expected --- .../Remote/Core/AbstractAssetProvider.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 2cc7bf9e2192a..cff100fd3b5a7 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -192,20 +192,32 @@ public static async Task GetAssetsAsync( public static async Task> GetAssetsArrayAsync( this AbstractAssetProvider assetProvider, AssetPath assetPath, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class { + // Note: nothing stops 'checksums' from having multiple identical checksums in it. First, collapse this down to + // a set so we're only asking about unique checksums. using var _1 = PooledHashSet.GetInstance(out var checksumSet); #if NET checksumSet.EnsureCapacity(checksums.Children.Length); #endif checksumSet.AddAll(checksums.Children); - using var _ = ArrayBuilder.GetInstance(checksumSet.Count, out var builder); + using var _2 = PooledDictionary.GetInstance(out var checksumToAsset); await assetProvider.GetAssetHelper().GetAssetsAsync( assetPath, checksumSet, - static (checksum, asset, builder) => builder.Add(asset), - builder, + static (checksum, asset, checksumToAsset) => checksumToAsset.Add(checksum, asset), + checksumToAsset, cancellationToken).ConfigureAwait(false); - return builder.ToImmutableAndClear(); + // Note: GetAssetsAsync will only succeed if we actually found all our assets (it crashes otherwise). So we can + // just safely assume we can index into checksumToAsset here. + + // The result of GetAssetsArrayAsync wants the returned assets to be in the exact order of the checksums that + // were in 'checksums'. So now fetch the assets in that order, even if we found them in an entirely different + // order. + var result = new FixedSizeArrayBuilder(checksums.Children.Length); + foreach (var checksum in checksums.Children) + result.Add(checksumToAsset[checksum]); + + return result.MoveToImmutable(); } } From 21d8c071f72a429f2720437de66d005c850d6193 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 14:55:34 -0700 Subject: [PATCH 268/292] Docs --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index cff100fd3b5a7..fd581fc8f0deb 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -189,6 +189,10 @@ public static async Task GetAssetsAsync( await assetProvider.GetAssetsAsync(assetPath, checksumSet, callback, arg, cancellationToken).ConfigureAwait(false); } + /// + /// Returns an array of assets, corresponding to all the checksums found in the given . + /// The assets will be returned in the order corresponding to their checksum in . + /// public static async Task> GetAssetsArrayAsync( this AbstractAssetProvider assetProvider, AssetPath assetPath, ChecksumCollection checksums, CancellationToken cancellationToken) where T : class { From 0eb4b7c9d087f7f246949bcfbb713d4b78c9245f Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Wed, 24 Apr 2024 15:03:50 -0700 Subject: [PATCH 269/292] Removed abstract IDiagnosticSourceProvider classes --- ...bstractDocumentDiagnosticSourceProvider.cs | 28 ------------------- ...ntaxAndSemanticDiagnosticSourceProvider.cs | 25 ++++++++++------- .../DocumentTaskDiagnosticSourceProvider.cs | 20 ++++++++++--- ...cs => WorkspaceDiagnosticSourceHelpers.cs} | 14 ++-------- ...mentsAndProjectDiagnosticSourceProvider.cs | 11 +++++--- ...ocumentNonLocalDiagnosticSourceProvider.cs | 18 ++++++++---- ...EditAndContinueDiagnosticSourceProvider.cs | 20 ++++++++++--- ...EditAndContinueDiagnosticSourceProvider.cs | 7 +++-- .../WorkspaceTaskDiagnosticSourceProvider.cs | 12 ++++---- 9 files changed, 81 insertions(+), 74 deletions(-) delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/AbstractDocumentDiagnosticSourceProvider.cs rename src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/{AbstractWorkspaceDiagnosticSourceProvider.cs => WorkspaceDiagnosticSourceHelpers.cs} (77%) rename src/Features/LanguageServer/Protocol/Handler/{Diagnostics/DiagnosticSourceProviders => EditAndContinue}/DocumentEditAndContinueDiagnosticSourceProvider.cs (51%) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/AbstractDocumentDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/AbstractDocumentDiagnosticSourceProvider.cs deleted file mode 100644 index 7bcc29af01015..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/AbstractDocumentDiagnosticSourceProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -internal abstract class AbstractDocumentDiagnosticSourceProvider(string name) : IDiagnosticSourceProvider where TDocument : TextDocument -{ - public bool IsDocument => true; - public string Name => name; - - protected abstract IDiagnosticSource? CreateDiagnosticSource(TDocument document); - - public virtual ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - if (context.GetTrackedDocument() is { } document && - CreateDiagnosticSource(document) is { } source) - { - return new([source]); - } - - return new([]); - } -} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs index 7209637a6ae67..2f0f50ec3f604 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentSyntaxAndSemanticDiagnosticSourceProvider.cs @@ -3,26 +3,31 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal abstract class AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider : AbstractDocumentDiagnosticSourceProvider +internal abstract class AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( + IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind kind, string sourceName) + : IDiagnosticSourceProvider { - private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; - private readonly DiagnosticKind _kind; + public bool IsDocument => true; + public string Name => sourceName; - public AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(IDiagnosticAnalyzerService diagnosticAnalyzerService, - DiagnosticKind kind, string sourceName) : base(sourceName) + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { - _diagnosticAnalyzerService = diagnosticAnalyzerService; - _kind = kind; - } + if (context.GetTrackedDocument() is { } document) + { + return new([new DocumentDiagnosticSource(diagnosticAnalyzerService, kind, document)]); + } - protected override IDiagnosticSource? CreateDiagnosticSource(TextDocument document) - => new DocumentDiagnosticSource(_diagnosticAnalyzerService, _kind, document); + return new([]); + } [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentTaskDiagnosticSourceProvider.cs index e5c3c1a9f9c4a..5276a1ff9a98b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentTaskDiagnosticSourceProvider.cs @@ -3,7 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; @@ -12,10 +15,19 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) - : AbstractDocumentDiagnosticSourceProvider(PullDiagnosticCategories.Task) +internal sealed class DocumentTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) : IDiagnosticSourceProvider { - protected override IDiagnosticSource? CreateDiagnosticSource(Document document) - => new TaskListDiagnosticSource(document, globalOptions); + public bool IsDocument => true; + public string Name => PullDiagnosticCategories.Task; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } document) + { + return new([new TaskListDiagnosticSource(document, globalOptions)]); + } + + return new([]); + } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/AbstractWorkspaceDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs similarity index 77% rename from src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/AbstractWorkspaceDiagnosticSourceProvider.cs rename to src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs index 3fe86dab35b04..de97a77269cbe 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/AbstractWorkspaceDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs @@ -5,22 +5,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal abstract class AbstractWorkspaceDiagnosticSourceProvider(string name) : IDiagnosticSourceProvider +internal static class WorkspaceDiagnosticSourceHelpers { - public bool IsDocument => false; - public string Name => name; - - public abstract ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); - - protected static IEnumerable GetProjectsInPriorityOrder( - Solution solution, ImmutableArray supportedLanguages) + public static IEnumerable GetProjectsInPriorityOrder(this Solution solution, ImmutableArray supportedLanguages) { return GetProjectsInPriorityOrderWorker(solution) .WhereNotNull() @@ -47,7 +39,7 @@ protected static IEnumerable GetProjectsInPriorityOrder( } } - protected static bool ShouldSkipDocument(RequestContext context, TextDocument document) + public static bool ShouldSkipDocument(this RequestContext context, TextDocument document) { // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). // Each handler treats those as separate worlds that they are responsible for. diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs index 2a13c533b9094..a00062c76ca91 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -23,8 +23,11 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, [Import] IGlobalOptionService globalOptions) - : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.WorkspaceDocumentsAndProject) + : IDiagnosticSourceProvider { + public bool IsDocument => false; + public string Name => PullDiagnosticCategories.WorkspaceDocumentsAndProject; + /// /// There are three potential sources for reporting workspace diagnostics: /// @@ -43,7 +46,7 @@ internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( /// If full solution analysis is disabled AND code analysis was never executed for the given project, /// we have no workspace diagnostics to report and bail out. /// - public override async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); @@ -53,7 +56,7 @@ public override async ValueTask> CreateDiagnos var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; var codeAnalysisService = solution.Services.GetRequiredService(); - foreach (var project in GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) + foreach (var project in solution.GetProjectsInPriorityOrder(context.SupportedLanguages)) await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); return result.ToImmutableAndClear(); @@ -86,7 +89,7 @@ void AddDocumentSources(IEnumerable documents) { foreach (var document in documents) { - if (!ShouldSkipDocument(context, document)) + if (!context.ShouldSkipDocument(document)) { // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. var documentDiagnosticSource = fullSolutionAnalysisEnabled diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs index cba206efb5457..41f9d51b363d4 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -3,7 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; @@ -17,19 +20,22 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( [Import] IGlobalOptionService globalOptions, [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : AbstractDocumentDiagnosticSourceProvider(NonLocal) + : IDiagnosticSourceProvider { public const string NonLocal = "NonLocal_B69807DB-28FB-4846-884A-1152E54C8B62"; + public bool IsDocument => true; + public string Name => NonLocal; - protected override IDiagnosticSource? CreateDiagnosticSource(TextDocument textDocument) + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { // Non-local document diagnostics are reported only when full solution analysis is enabled for analyzer execution. - if (globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) != BackgroundAnalysisScope.FullSolution) + if (context.GetTrackedDocument() is { } textDocument && + globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) == BackgroundAnalysisScope.FullSolution) { - return null; + // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. + return new([new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, a => !a.IsCompilerAnalyzer())]); } - // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - return new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, a => !a.IsCompilerAnalyzer()); + return new([]); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs similarity index 51% rename from src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentEditAndContinueDiagnosticSourceProvider.cs rename to src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs index f60eb176238e0..63e5866c67b1e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/DocumentEditAndContinueDiagnosticSourceProvider.cs @@ -3,7 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Composition; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host.Mef; @@ -12,9 +15,18 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class DocumentEditAndContinueDiagnosticSourceProvider() - : AbstractDocumentDiagnosticSourceProvider(PullDiagnosticCategories.EditAndContinue) +internal sealed class DocumentEditAndContinueDiagnosticSourceProvider() : IDiagnosticSourceProvider { - protected override IDiagnosticSource? CreateDiagnosticSource(Document document) - => EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document); + public bool IsDocument => true; + public string Name => PullDiagnosticCategories.EditAndContinue; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + { + if (context.GetTrackedDocument() is { } document) + { + return new([EditAndContinueDiagnosticSource.CreateOpenDocumentSource(document)]); + } + + return new([]); + } } diff --git a/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs index e47e52bc8bc60..847a909a2ff25 100644 --- a/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/EditAndContinue/WorkspaceEditAndContinueDiagnosticSourceProvider.cs @@ -16,9 +16,12 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.EditAndContinue) +internal sealed class WorkspaceEditAndContinueDiagnosticSourceProvider() : IDiagnosticSourceProvider { - public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + public bool IsDocument => false; + public string Name => PullDiagnosticCategories.EditAndContinue; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); return EditAndContinueDiagnosticSource.CreateWorkspaceDiagnosticSourcesAsync(context.Solution!, document => context.IsTracking(document.GetURI()), cancellationToken); diff --git a/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs index 33755784ebd49..275a170c36019 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs @@ -18,10 +18,12 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) - : AbstractWorkspaceDiagnosticSourceProvider(PullDiagnosticCategories.Task) +internal sealed class WorkspaceTaskDiagnosticSourceProvider([Import] IGlobalOptionService globalOptions) : IDiagnosticSourceProvider { - public override ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + public bool IsDocument => false; + public string Name => PullDiagnosticCategories.Task; + + public ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); @@ -29,11 +31,11 @@ public override ValueTask> CreateDiagnosticSou if (globalOptions.GetTaskListOptions().ComputeForClosedFiles) { using var _ = ArrayBuilder.GetInstance(out var result); - foreach (var project in GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) + foreach (var project in context.Solution.GetProjectsInPriorityOrder(context.SupportedLanguages)) { foreach (var document in project.Documents) { - if (!ShouldSkipDocument(context, document)) + if (!context.ShouldSkipDocument(document)) result.Add(new TaskListDiagnosticSource(document, globalOptions)); } } From 33c28ac2627d18f30e27d9bed1dbb5674a4ec957 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 15:04:34 -0700 Subject: [PATCH 270/292] Update src/Workspaces/Remote/Core/AbstractAssetProvider.cs --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index fd581fc8f0deb..4c50ad11a60ee 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -215,9 +215,7 @@ await assetProvider.GetAssetHelper().GetAssetsAsync( // Note: GetAssetsAsync will only succeed if we actually found all our assets (it crashes otherwise). So we can // just safely assume we can index into checksumToAsset here. - // The result of GetAssetsArrayAsync wants the returned assets to be in the exact order of the checksums that - // were in 'checksums'. So now fetch the assets in that order, even if we found them in an entirely different - // order. + // Order assets in the same order their checksums were requested in. var result = new FixedSizeArrayBuilder(checksums.Children.Length); foreach (var checksum in checksums.Children) result.Add(checksumToAsset[checksum]); From 7053bf51ebf6e0a6aaa404db6780dad773124ec1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 15:15:49 -0700 Subject: [PATCH 271/292] Add docs and asset --- src/Workspaces/Remote/Core/AbstractAssetProvider.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs index 4c50ad11a60ee..ac97eb85341e4 100644 --- a/src/Workspaces/Remote/Core/AbstractAssetProvider.cs +++ b/src/Workspaces/Remote/Core/AbstractAssetProvider.cs @@ -208,14 +208,18 @@ public static async Task> GetAssetsArrayAsync( await assetProvider.GetAssetHelper().GetAssetsAsync( assetPath, checksumSet, + // Calling .Add here is safe. As checksum-set is a unique set of checksums, we'll never have collions here. static (checksum, asset, checksumToAsset) => checksumToAsset.Add(checksum, asset), checksumToAsset, cancellationToken).ConfigureAwait(false); // Note: GetAssetsAsync will only succeed if we actually found all our assets (it crashes otherwise). So we can // just safely assume we can index into checksumToAsset here. + Contract.ThrowIfTrue(checksumToAsset.Count != checksumSet.Count); - // Order assets in the same order their checksums were requested in. + // The result of GetAssetsArrayAsync wants the returned assets to be in the exact order of the checksums that + // were in 'checksums'. So now fetch the assets in that order, even if we found them in an entirely different + // order. var result = new FixedSizeArrayBuilder(checksums.Children.Length); foreach (var checksum in checksums.Children) result.Add(checksumToAsset[checksum]); From ad637ba91f357a17143a6c8d1dc2edb86c2d5a33 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Wed, 24 Apr 2024 15:17:58 -0700 Subject: [PATCH 272/292] More feedback --- ...AbstractWorkspacePullDiagnosticsHandler.cs | 3 +- .../DiagnosticSourceManager.cs | 39 ++++++++++++++ .../WorkspaceDiagnosticSourceHelpers.cs | 4 +- ...mentsAndProjectDiagnosticSourceProvider.cs | 4 +- .../AggregatedDocumentDiagnosticSource.cs | 53 ------------------- .../WorkspaceTaskDiagnosticSourceProvider.cs | 4 +- .../Internal/HotReloadDiagnosticManager.cs | 6 +-- 7 files changed, 49 insertions(+), 64 deletions(-) delete mode 100644 src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AggregatedDocumentDiagnosticSource.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index c3f7cc2b5e7cd..7e3d6e4619791 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -19,6 +19,7 @@ internal abstract class AbstractWorkspacePullDiagnosticsHandler /// Flag that represents whether the LSP view of the world has changed. @@ -44,8 +45,6 @@ protected AbstractWorkspacePullDiagnosticsHandler( _workspaceManager.LspTextChanged += OnLspTextChanged; } - protected IDiagnosticSourceManager DiagnosticSourceManager { get; } - public void Dispose() { _workspaceManager.LspTextChanged -= OnLspTextChanged; diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs index 1606acce7bb54..ed057b7745b3e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs @@ -113,4 +113,43 @@ public static ImmutableArray AggregateSourcesIfNeeded(Immutab return sources; } + + /// + /// Aggregates multiple s into a single source. + /// + /// Sources to aggregate + /// + /// Aggregation is usually needed for clients like VS Code which supports single source per request. + /// + private sealed class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource + { + public static ImmutableArray AggregateIfNeeded(IEnumerable sources) + { + var result = sources.ToImmutableArray(); + if (result.Length > 1) + { + result = [new AggregatedDocumentDiagnosticSource(result)]; + } + + return result; + } + + public bool IsLiveSource() => true; + public Project GetProject() => sources[0].GetProject(); + public ProjectOrDocumentId GetId() => sources[0].GetId(); + public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier(); + public string ToDisplayString() => $"{this.GetType().Name}: count={sources.Length}"; + + public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var diagnostics); + foreach (var source in sources) + { + var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); + diagnostics.AddRange(namedDiagnostics); + } + + return diagnostics.ToImmutableAndClear(); + } + } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs index de97a77269cbe..8956840dbbfa2 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDiagnosticSourceHelpers.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal static class WorkspaceDiagnosticSourceHelpers { - public static IEnumerable GetProjectsInPriorityOrder(this Solution solution, ImmutableArray supportedLanguages) + public static IEnumerable GetProjectsInPriorityOrder(Solution solution, ImmutableArray supportedLanguages) { return GetProjectsInPriorityOrderWorker(solution) .WhereNotNull() @@ -39,7 +39,7 @@ public static IEnumerable GetProjectsInPriorityOrder(this Solution solu } } - public static bool ShouldSkipDocument(this RequestContext context, TextDocument document) + public static bool ShouldSkipDocument(RequestContext context, TextDocument document) { // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). // Each handler treats those as separate worlds that they are responsible for. diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs index a00062c76ca91..9daa0cf91dd5c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -56,7 +56,7 @@ public async ValueTask> CreateDiagnosticSource var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService()?.EnableDiagnosticsInSourceGeneratedFiles == true; var codeAnalysisService = solution.Services.GetRequiredService(); - foreach (var project in solution.GetProjectsInPriorityOrder(context.SupportedLanguages)) + foreach (var project in WorkspaceDiagnosticSourceHelpers.GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); return result.ToImmutableAndClear(); @@ -89,7 +89,7 @@ void AddDocumentSources(IEnumerable documents) { foreach (var document in documents) { - if (!context.ShouldSkipDocument(document)) + if (!WorkspaceDiagnosticSourceHelpers.ShouldSkipDocument(context, document)) { // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. var documentDiagnosticSource = fullSolutionAnalysisEnabled diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AggregatedDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AggregatedDocumentDiagnosticSource.cs deleted file mode 100644 index 9a779fd5967d8..0000000000000 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AggregatedDocumentDiagnosticSource.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; - -/// -/// Aggregates multiple s into a single source. -/// -/// Sources to aggregate -/// -/// Aggregation is usually needed for clients like VS Code which supports single source per request. -/// -internal sealed class AggregatedDocumentDiagnosticSource(ImmutableArray sources) : IDiagnosticSource -{ - public static ImmutableArray AggregateIfNeeded(IEnumerable sources) - { - var result = sources.ToImmutableArray(); - if (result.Length > 1) - { - result = [new AggregatedDocumentDiagnosticSource(result)]; - } - - return result; - } - - public bool IsLiveSource() => true; - public Project GetProject() => sources[0].GetProject(); - public ProjectOrDocumentId GetId() => sources[0].GetId(); - public TextDocumentIdentifier? GetDocumentIdentifier() => sources[0].GetDocumentIdentifier(); - public string ToDisplayString() => $"{this.GetType().Name}: count={sources.Length}"; - - public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var diagnostics); - foreach (var source in sources) - { - var namedDiagnostics = await source.GetDiagnosticsAsync(context, cancellationToken).ConfigureAwait(false); - diagnostics.AddRange(namedDiagnostics); - } - - return diagnostics.ToImmutableAndClear(); - } -} diff --git a/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs index 275a170c36019..6620285cfea20 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs @@ -31,11 +31,11 @@ public ValueTask> CreateDiagnosticSourcesAsync if (globalOptions.GetTaskListOptions().ComputeForClosedFiles) { using var _ = ArrayBuilder.GetInstance(out var result); - foreach (var project in context.Solution.GetProjectsInPriorityOrder(context.SupportedLanguages)) + foreach (var project in WorkspaceDiagnosticSourceHelpers.GetProjectsInPriorityOrder(context.Solution, context.SupportedLanguages)) { foreach (var document in project.Documents) { - if (!context.ShouldSkipDocument(document)) + if (!WorkspaceDiagnosticSourceHelpers.ShouldSkipDocument(context, document)) result.Add(new TaskListDiagnosticSource(document, globalOptions)); } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs index 1f52edbb91297..8705fcc27b18f 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class HotReloadDiagnosticManager([Import] IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager { - private readonly object syncLock = new(); + private readonly object _syncLock = new(); private ImmutableArray _providers = ImmutableArray.Empty; ImmutableArray IHotReloadDiagnosticManager.Providers => _providers; void IHotReloadDiagnosticManager.RequestRefresh() => diagnosticsRefresher.RequestWorkspaceRefresh(); @@ -26,7 +26,7 @@ void IHotReloadDiagnosticManager.Register(IEnumerable providers) { - lock (syncLock) + lock (_syncLock) { foreach (var provider in providers) _providers = _providers.Remove(provider); From a1b4888898eaa4e541f7b69710ee3741cbb6394c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 16:40:31 -0700 Subject: [PATCH 273/292] Add test --- .../Test.Next/Services/AssetProviderTests.cs | 75 +++++++++++++++++++ .../ServiceHub/Host/SolutionAssetCache.cs | 9 +++ 2 files changed, 84 insertions(+) diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index f7424abe817b4..f9826d7e8aafe 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -146,5 +147,79 @@ public async Task TestProjectSynchronization() TestUtils.VerifyAssetStorage(map, storage); } + + [Fact] + public async Task TestAssetArrayOrdering() + { + var code1 = @"class Test1 { void Method() { } }"; + var code2 = @"class Test2 { void Method() { } }"; + + using var workspace = TestWorkspace.CreateCSharp([code1, code2]); + var project = workspace.CurrentSolution.Projects.First(); + + await project.State.GetChecksumAsync(CancellationToken.None); + + var map = await project.GetAssetMapAsync(CancellationToken.None); + + using var remoteWorkspace = CreateRemoteWorkspace(); + + var sessionId = Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())); + var storage = new SolutionAssetCache(); + var assetSource = new OrderedAssetSource(workspace.Services.GetService(), map); + + var service = new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); + + using var _ = ArrayBuilder.GetInstance(out var allProjectChecksums); + var stateChecksums = await project.State.GetStateChecksumsAsync(CancellationToken.None); + + var textChecksums = stateChecksums.Documents.TextChecksums; + var textChecksumsReversed = new ChecksumCollection(textChecksums.Children.Reverse().ToImmutableArray()); + + var documents = await service.GetAssetsArrayAsync( + AssetPath.FullLookupForTesting, textChecksums, CancellationToken.None); + Assert.True(documents.Length == 2); + + storage.GetTestAccessor().Clear(); + var documentsReversed = await service.GetAssetsArrayAsync( + AssetPath.FullLookupForTesting, textChecksumsReversed, CancellationToken.None); + Assert.True(documentsReversed.Length == 2); + + Assert.True(documents.Select(d => d.ContentChecksum).SequenceEqual(documentsReversed.Reverse().Select(d => d.ContentChecksum))); + } + + private sealed class OrderedAssetSource( + ISerializerService serializerService, + IReadOnlyDictionary map) : IAssetSource + { + public ValueTask GetAssetsAsync( + Checksum solutionChecksum, + AssetPath assetPath, + ReadOnlyMemory checksums, + ISerializerService deserializerService, + Action callback, + TArg arg, + CancellationToken cancellationToken) + { + foreach (var (checksum, asset) in map) + { + if (checksums.Span.IndexOf(checksum) >= 0) + { + using var stream = new MemoryStream(); + using (var writer = new ObjectWriter(stream, leaveOpen: true)) + { + serializerService.Serialize(asset, writer, cancellationToken); + } + + stream.Position = 0; + using var reader = ObjectReader.GetReader(stream, leaveOpen: true); + var deserialized = deserializerService.Deserialize(asset.GetWellKnownSynchronizationKind(), reader, cancellationToken); + Contract.ThrowIfNull(deserialized); + callback(checksum, (T)deserialized, arg); + } + } + + return ValueTaskFactory.CompletedTask; + } + } } } diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs index 11067ca427033..b409b3b8e28db 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs @@ -267,5 +267,14 @@ public Entry(object @object) Object = @object; } } + + public TestAccessor GetTestAccessor() + => new(this); + + public readonly struct TestAccessor(SolutionAssetCache cache) + { + public void Clear() + => cache._assets.Clear(); + } } From 7d27e263c8e73882a318fd855ad65c1bb25a1354 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 17:05:42 -0700 Subject: [PATCH 274/292] Add docs --- .../ITemporaryStorageService.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index c0c7b5c80f731..4cbd9198ae99c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -27,27 +27,26 @@ internal interface ITemporaryStorageServiceInternal : IWorkspaceService /// be used to identify the data across processes allowing it to be read back in in any process. /// /// - /// This type is used for two purposes. - /// - /// - /// Dumping metadata to disk. This then allowing them to be read in by mapping their data into types like . It also allows them to be read in by our server process, without having to transmit - /// the data over the wire. For this use case, we never dispose of the handle, opting to keep things simple by - /// having the host and server not have to coordinate on the lifetime of the data. - /// - /// - /// Dumping large compiler command lines to disk to purge them from main memory. Some of these strings are enormous - /// (many MB large), and will get into the LOH. This allows us to dump the data, knowing we can perfectly - /// reconstruct it when needed. In this case, we do dispose of the handle, as we don't need to keep the data around - /// when we get the next large compiler command line. - /// - /// - /// Note: The stream provided must support . The stream will also be reset to 0 within this method. The caller does not need to reset the stream + /// This type is primarily used to allow dumping metadata to disk. This then allowing them to be read in by mapping + /// their data into types like . It also allows them to be read in by our server + /// process, without having to transmit the data over the wire. + /// Note: The stream provided must support . The stream will also be reset to + /// 0 within this method. The caller does not need to reset the stream /// itself. /// ITemporaryStorageStreamHandle WriteToTemporaryStorage(Stream stream, CancellationToken cancellationToken); + /// + /// Write the provided to a new memory-mapped-file. Returns a handle to the data that can + /// be used to identify the data across processes allowing it to be read back in in any process. + /// + /// + /// This type is primarily used to allow dumping source texts to disk. This then allowing them to be read in by + /// mapping their data into types like . It also allows them + /// to be read in by our server process, without having to transmit the data over the wire. + /// ITemporaryStorageTextHandle WriteToTemporaryStorage(SourceText text, CancellationToken cancellationToken); + + /// "/> Task WriteToTemporaryStorageAsync(SourceText text, CancellationToken cancellationToken); } From 4c7a01c16d3f50bfec3cb0adaef0bcfaed25fde8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 24 Apr 2024 17:43:54 -0700 Subject: [PATCH 275/292] Update obsoletion to be an error for a few types --- .../Workspace/Host/TemporaryStorage/ITemporaryStorage.cs | 4 ++-- .../Host/TemporaryStorage/ITemporaryStorageService.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs index aaac0a3ac1e6c..9466f7fa7d175 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorage.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Host; -[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.")] +[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.", error: true)] public interface ITemporaryTextStorage : IDisposable { SourceText ReadText(CancellationToken cancellationToken = default); @@ -21,7 +21,7 @@ public interface ITemporaryTextStorage : IDisposable Task WriteTextAsync(SourceText text, CancellationToken cancellationToken = default); } -[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.")] +[Obsolete("Roslyn no longer exports a mechanism to store arbitrary data in-memory.", error: true)] public interface ITemporaryStreamStorage : IDisposable { Stream ReadStream(CancellationToken cancellationToken = default); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs index b9d1f9d530efc..342adac72226f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/TemporaryStorage/ITemporaryStorageService.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.Host; -[Obsolete("API is no longer available")] +[Obsolete("API is no longer available", error: true)] public interface ITemporaryStorageService : IWorkspaceService { ITemporaryStreamStorage CreateTemporaryStreamStorage(CancellationToken cancellationToken = default); From 536671e7953ef61274720e7129631f8d65d507e9 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Thu, 25 Apr 2024 09:06:09 -0700 Subject: [PATCH 276/292] Tweaks --- .../Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs | 2 +- ...crosoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs index 41f9d51b363d4..9b8f9116ebc19 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -22,7 +22,7 @@ internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) : IDiagnosticSourceProvider { - public const string NonLocal = "NonLocal_B69807DB-28FB-4846-884A-1152E54C8B62"; + public const string NonLocal = nameof(NonLocal); public bool IsDocument => true; public string Name => NonLocal; diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj b/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj index ada2581f782b5..f63b387f65552 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.csproj @@ -16,7 +16,7 @@ - + From 8293404bdf57d55e40baa79768cabb8c85041e75 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Thu, 25 Apr 2024 11:43:22 -0700 Subject: [PATCH 277/292] CR feedback --- .../AlwaysActivateInProcLanguageClient.cs | 4 +-- .../AbstractDocumentPullDiagnosticHandler.cs | 14 ++------ .../DiagnosticSourceManager.cs | 27 +++++++++------ .../IDiagnosticSourceManager.cs | 33 +++++++++++-------- .../IDiagnosticSourceProvider.cs | 6 ++-- ...ntPullDiagnosticsHandler_IOnInitialized.cs | 3 +- ...cePullDiagnosticsHandler_IOnInitialized.cs | 23 +++++-------- .../DocumentTaskDiagnosticSourceProvider.cs | 2 +- .../WorkspaceTaskDiagnosticSourceProvider.cs | 2 +- .../Contracts/HotReloadRequestContext.cs | 2 +- .../Contracts/IHotReloadDiagnosticSource.cs | 2 +- .../Internal/HotReloadDiagnosticManager.cs | 20 ++++++----- .../Internal/HotReloadDiagnosticSource.cs | 17 +++++----- .../HotReloadDiagnosticSourceProvider.cs | 25 ++++++++++---- .../InternalAPI.Unshipped.txt | 19 ++++++++--- 15 files changed, 111 insertions(+), 88 deletions(-) rename src/Features/LanguageServer/Protocol/Handler/{Diagnostics/DiagnosticSourceProviders => Tasks}/DocumentTaskDiagnosticSourceProvider.cs (99%) rename src/Features/LanguageServer/Protocol/Handler/{Task => Tasks}/WorkspaceTaskDiagnosticSourceProvider.cs (99%) diff --git a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs index 60eb7369d5785..dacbc7633a275 100644 --- a/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs +++ b/src/EditorFeatures/Core/LanguageServer/AlwaysActivateInProcLanguageClient.cs @@ -74,8 +74,8 @@ public override ServerCapabilities GetCapabilities(ClientCapabilities clientCapa serverCapabilities.DiagnosticProvider ??= new(); // VS does not distinguish between document and workspace diagnostics, so we need to merge them. - var diagnosticSourceNames = _diagnosticSourceManager.GetSourceNames(isDocument: true) - .Concat(_diagnosticSourceManager.GetSourceNames(isDocument: false)) + var diagnosticSourceNames = _diagnosticSourceManager.GetDocumentSourceProviderNames() + .Concat(_diagnosticSourceManager.GetWorkspaceSourceProviderNames()) .Distinct(); serverCapabilities.DiagnosticProvider = serverCapabilities.DiagnosticProvider with { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index 37df98082f052..106d2dc6bfc78 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -29,14 +29,6 @@ internal abstract class AbstractDocumentPullDiagnosticHandler> GetOrderedDiagnosticSourcesAsync(TDiagnosticsParams diagnosticsParams, string? requestDiagnosticCategory, RequestContext context, CancellationToken cancellationToken) - { - if (GetOpenDocument(context) is null) - return new([]); - - return DiagnosticSourceManager.CreateDocumentDiagnosticSourcesAsync(context, requestDiagnosticCategory, cancellationToken); - } - - private static TextDocument? GetOpenDocument(RequestContext context) { // Note: context.Document may be null in the case where the client is asking about a document that we have // since removed from the workspace. In this case, we don't really have anything to process. @@ -48,15 +40,15 @@ protected override ValueTask> GetOrderedDiagno if (textDocument is null) { context.TraceInformation("Ignoring diagnostics request because no text document was provided"); - return null; + return new([]); } if (!context.IsTracking(textDocument.GetURI())) { context.TraceWarning($"Ignoring diagnostics request for untracked document: {textDocument.GetURI()}"); - return null; + return new([]); } - return textDocument; + return DiagnosticSourceManager.CreateDocumentDiagnosticSourcesAsync(context, requestDiagnosticCategory, cancellationToken); } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs index ed057b7745b3e..6058141a7ec4f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs @@ -46,27 +46,31 @@ public DiagnosticSourceManager([ImportMany] IEnumerable - public ImmutableArray GetSourceNames(bool isDocument) - => (isDocument ? _nameToDocumentProviderMap : _nameToWorkspaceProviderMap).Keys.ToImmutableArray(); + public ImmutableArray GetDocumentSourceProviderNames() + => _nameToDocumentProviderMap.Keys.ToImmutableArray(); - public ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? sourceName, CancellationToken cancellationToken) - => CreateDiagnosticSourcesAsync(context, sourceName, _nameToDocumentProviderMap, isDocument: true, cancellationToken); + /// + public ImmutableArray GetWorkspaceSourceProviderNames() + => _nameToWorkspaceProviderMap.Keys.ToImmutableArray(); + + public ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken) + => CreateDiagnosticSourcesAsync(context, providerName, _nameToDocumentProviderMap, isDocument: true, cancellationToken); - public ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? sourceName, CancellationToken cancellationToken) - => CreateDiagnosticSourcesAsync(context, sourceName, _nameToWorkspaceProviderMap, isDocument: false, cancellationToken); + public ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken) + => CreateDiagnosticSourcesAsync(context, providerName, _nameToWorkspaceProviderMap, isDocument: false, cancellationToken); private static async ValueTask> CreateDiagnosticSourcesAsync( RequestContext context, - string? sourceName, + string? providerName, ImmutableDictionary nameToProviderMap, bool isDocument, CancellationToken cancellationToken) { - if (sourceName != null) + if (providerName != null) { // VS does not distinguish between document and workspace sources. Thus it can request // document diagnostics with workspace source name. We need to handle this case. - if (nameToProviderMap.TryGetValue(sourceName, out var provider)) + if (nameToProviderMap.TryGetValue(providerName, out var provider)) return await provider.CreateDiagnosticSourcesAsync(context, cancellationToken).ConfigureAwait(false); return []; @@ -105,7 +109,10 @@ public static ImmutableArray AggregateSourcesIfNeeded(Immutab } else { - // For workspace we need to group sources by source id and IsLiveSource + // We ASSUME that all sources with the same ProjectOrDocumentID and IsLiveSource + // will have same value for GetDocumentIdentifier and GetProject(). Thus can be + // aggregated in a single source which will return same values. See + // AggregatedDocumentDiagnosticSource implementation for more details. sources = sources.GroupBy(s => (s.GetId(), s.IsLiveSource()), s => s) .SelectMany(g => AggregatedDocumentDiagnosticSource.AggregateIfNeeded(g)) .ToImmutableArray(); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs index 0c7eddd434578..51e0ef8ff0e10 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceManager.cs @@ -9,29 +9,34 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.DiagnosticSources; /// -/// Manages the diagnostic sources that provide diagnostics for the language server. +/// Provides centralized/singleton management of MEF based s. +/// Consumers - like diagnostic handlers - use it to get diagnostics from one or more providers. /// internal interface IDiagnosticSourceManager { /// - /// Returns the names of all the sources that provide diagnostics for the given . + /// Returns the names of document level s. /// - /// for document sources and for workspace sources. - ImmutableArray GetSourceNames(bool isDocument); + ImmutableArray GetDocumentSourceProviderNames(); /// - /// Creates document diagnostic sources for the given . + /// Returns the names of workspace level s. /// - /// The context. - /// Source name. - /// The cancellation token. - ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? sourceName, CancellationToken cancellationToken); + ImmutableArray GetWorkspaceSourceProviderNames(); /// - /// Creates workspace diagnostic sources for the given . + /// Creates document diagnostic sources for the given . /// - /// The context. - /// Source name. - /// The cancellation token. - ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? sourceName, CancellationToken cancellationToken); + /// + /// Optional provider name. If then diagnostics from all providers are used. + /// A cancellation token that can be used to cancel the request processing. + ValueTask> CreateDocumentDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken); + + /// + /// Creates workspace diagnostic sources for the given . + /// + /// + /// Optional provider name. If not specified then diagnostics from all providers are used. + /// A cancellation token that can be used to cancel the request processing. + ValueTask> CreateWorkspaceDiagnosticSourcesAsync(RequestContext context, string? providerName, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs index bcac55c7deb64..daeb1b4d71aee 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/IDiagnosticSourceProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal interface IDiagnosticSourceProvider { /// - /// True if this provider is for documents, false if it is for workspace. + /// if this provider is for documents, if it is for workspace. /// bool IsDocument { get; } @@ -26,8 +26,8 @@ internal interface IDiagnosticSourceProvider /// /// Creates the diagnostic sources. /// - /// The context. - /// The cancellation token. + /// + /// A cancellation token that can be used to cancel the request processing. ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs index f2a7244eeeec8..a8332219d1d12 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler_IOnInitialized.cs @@ -23,7 +23,8 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ // TODO: Hookup an option changed handler for changes to BackgroundAnalysisScopeOption // to dynamically register/unregister the non-local document diagnostic source. - var sources = DiagnosticSourceManager.GetSourceNames(isDocument: true).Where(source => source != PullDiagnosticCategories.Task); + // Task diagnostics shouldn't be reported through VSCode (it has its own task stuff). Additional cleanup needed. + var sources = DiagnosticSourceManager.GetDocumentSourceProviderNames().Where(source => source != PullDiagnosticCategories.Task); var registrations = sources.Select(FromSourceName).ToArray(); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs index cf53f3cbe8846..2e34f2dbccf03 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler_IOnInitialized.cs @@ -15,26 +15,21 @@ public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, Requ { if (clientCapabilities?.TextDocument?.Diagnostic?.DynamicRegistration is true) { - var sources = DiagnosticSourceManager.GetSourceNames(isDocument: false); + var providerNames = DiagnosticSourceManager.GetWorkspaceSourceProviderNames(); await _clientLanguageServerManager.SendRequestAsync( methodName: Methods.ClientRegisterCapabilityName, @params: new RegistrationParams { - Registrations = sources.Select(FromSourceName).ToArray() + Registrations = providerNames.Select(name => new Registration + { + // Due to https://github.com/microsoft/language-server-protocol/issues/1723 + // we need to use textDocument/diagnostic instead of workspace/diagnostic + Method = Methods.TextDocumentDiagnosticName, + Id = name, + RegisterOptions = new DiagnosticRegistrationOptions { Identifier = name, InterFileDependencies = true, WorkDoneProgress = true } + }).ToArray() }, cancellationToken).ConfigureAwait(false); - - Registration FromSourceName(string sourceName) - { - return new() - { - // Due to https://github.com/microsoft/language-server-protocol/issues/1723 - // we need to use textDocument/diagnostic instead of workspace/diagnostic - Method = Methods.TextDocumentDiagnosticName, - Id = sourceName, - RegisterOptions = new DiagnosticRegistrationOptions { Identifier = sourceName, InterFileDependencies = true, WorkDoneProgress = true } - }; - } } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs similarity index 99% rename from src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentTaskDiagnosticSourceProvider.cs rename to src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs index 5276a1ff9a98b..6b63bdf91f220 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DocumentTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Tasks/DocumentTaskDiagnosticSourceProvider.cs @@ -10,7 +10,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Tasks; [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] diff --git a/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs b/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs similarity index 99% rename from src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs rename to src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs index 6620285cfea20..53b1bf7e03cd5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Task/WorkspaceTaskDiagnosticSourceProvider.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Tasks/WorkspaceTaskDiagnosticSourceProvider.cs @@ -13,7 +13,7 @@ using Microsoft.CodeAnalysis.TaskList; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Tasks; [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs index 6e879f2535158..c7b511ee906c2 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/HotReloadRequestContext.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; -internal class HotReloadRequestContext(RequestContext context) +internal sealed class HotReloadRequestContext(RequestContext context) { internal LSP.ClientCapabilities ClientCapabilities => context.GetRequiredClientCapabilities(); public TextDocument? TextDocument => context.TextDocument; diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs index 05f1cee575b41..85a8004f1db8d 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Contracts/IHotReloadDiagnosticSource.cs @@ -16,7 +16,7 @@ internal interface IHotReloadDiagnosticSource /// /// Text document for which diagnostics are provided. /// - TextDocument TextDocument { get; } + DocumentId DocumentId { get; } /// /// Provides list of diagnostics for the given document. diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs index 8705fcc27b18f..8a6071ea4228a 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticManager.cs @@ -18,11 +18,12 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; internal sealed class HotReloadDiagnosticManager([Import] IDiagnosticsRefresher diagnosticsRefresher) : IHotReloadDiagnosticManager { private readonly object _syncLock = new(); - private ImmutableArray _providers = ImmutableArray.Empty; - ImmutableArray IHotReloadDiagnosticManager.Providers => _providers; - void IHotReloadDiagnosticManager.RequestRefresh() => diagnosticsRefresher.RequestWorkspaceRefresh(); + public ImmutableArray Providers { get; private set; } = []; - void IHotReloadDiagnosticManager.Register(IEnumerable providers) + public void RequestRefresh() + => diagnosticsRefresher.RequestWorkspaceRefresh(); + + public void Register(IEnumerable providers) { // We use array instead of e.g. HashSet because we expect the number of sources to be small. // Usually 2, one workspace and one document provider. @@ -30,18 +31,19 @@ void IHotReloadDiagnosticManager.Register(IEnumerable providers) + public void Unregister(IEnumerable providers) { lock (_syncLock) { - foreach (var provider in providers) - _providers = _providers.Remove(provider); + foreach (var provider in Providers) + Providers = Providers.Remove(provider); } } + } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs index c7b0e58584388..64d157b4a1343 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSource.cs @@ -15,19 +15,18 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; -internal sealed class HotReloadDiagnosticSource(IHotReloadDiagnosticSource source) : IDiagnosticSource +internal sealed class HotReloadDiagnosticSource(IHotReloadDiagnosticSource source, TextDocument textDocument) : IDiagnosticSource { - async Task> IDiagnosticSource.GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) + public async Task> GetDiagnosticsAsync(RequestContext context, CancellationToken cancellationToken) { var diagnostics = await source.GetDiagnosticsAsync(new HotReloadRequestContext(context), cancellationToken).ConfigureAwait(false); - var document = source.TextDocument; - var result = diagnostics.Select(diagnostic => DiagnosticData.Create(diagnostic, document)).ToImmutableArray(); + var result = diagnostics.Select(diagnostic => DiagnosticData.Create(diagnostic, textDocument)).ToImmutableArray(); return result; } - TextDocumentIdentifier? IDiagnosticSource.GetDocumentIdentifier() => new() { Uri = source.TextDocument.GetURI() }; - ProjectOrDocumentId IDiagnosticSource.GetId() => new(source.TextDocument.Id); - Project IDiagnosticSource.GetProject() => source.TextDocument.Project; - bool IDiagnosticSource.IsLiveSource() => true; - string IDiagnosticSource.ToDisplayString() => $"{this.GetType().Name}: {source.TextDocument.FilePath ?? source.TextDocument.Name} in {source.TextDocument.Project.Name}"; + public TextDocumentIdentifier? GetDocumentIdentifier() => new() { Uri = textDocument.GetURI() }; + public ProjectOrDocumentId GetId() => new(textDocument.Id); + public Project GetProject() => textDocument.Project; + public bool IsLiveSource() => true; + public string ToDisplayString() => $"{this.GetType().Name}: {textDocument.FilePath ?? textDocument.Name} in {textDocument.Project.Name}"; } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs index 2b8acf9787caa..495e586ea6f13 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs +++ b/src/Tools/ExternalAccess/VisualDiagnostics/Internal/HotReloadDiagnosticSourceProvider.cs @@ -3,10 +3,8 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts; @@ -19,11 +17,16 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal; internal abstract class HotReloadDiagnosticSourceProvider(IHotReloadDiagnosticManager diagnosticManager, bool isDocument) : IDiagnosticSourceProvider { - string IDiagnosticSourceProvider.Name => "HotReloadDiagnostics"; - bool IDiagnosticSourceProvider.IsDocument => isDocument; + public string Name => "HotReloadDiagnostics"; + public bool IsDocument => isDocument; - async ValueTask> IDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) + public async ValueTask> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) { + if (context.Solution is not Solution solution) + { + return ImmutableArray.Empty; + } + var hotReloadContext = new HotReloadRequestContext(context); using var _ = ArrayBuilder.GetInstance(out var sources); foreach (var provider in diagnosticManager.Providers) @@ -31,7 +34,17 @@ async ValueTask> IDiagnosticSourceProvider.Cre if (provider.IsDocument == isDocument) { var hotReloadSources = await provider.CreateDiagnosticSourcesAsync(hotReloadContext, cancellationToken).ConfigureAwait(false); - sources.AddRange(hotReloadSources.Select(s => new HotReloadDiagnosticSource(s))); + foreach (var hotReloadSource in hotReloadSources) + { + // Look for additional document first. Currently most common hot reload diagnostics come from *.xaml files. + TextDocument? textDocument = + solution.GetAdditionalDocument(hotReloadSource.DocumentId) ?? + solution.GetDocument(hotReloadSource.DocumentId); + if (textDocument != null) + { + sources.Add(new HotReloadDiagnosticSource(hotReloadSource, textDocument)); + } + } } } diff --git a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt index 70c2918b8f69c..019251a6dad44 100644 --- a/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt +++ b/src/Tools/ExternalAccess/VisualDiagnostics/InternalAPI.Unshipped.txt @@ -1,4 +1,3 @@ -abstract Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.ClientCapabilities.get -> Roslyn.LanguageServer.Protocol.ClientCapabilities! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext.HotReloadRequestContext(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context) -> void @@ -11,8 +10,8 @@ Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiag Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.RequestRefresh() -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager.Unregister(System.Collections.Generic.IEnumerable! providers) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.DocumentId.get -> Microsoft.CodeAnalysis.DocumentId! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! request, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource.TextDocument.get -> Microsoft.CodeAnalysis.TextDocument! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.HotReloadRequestContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSourceProvider.IsDocument.get -> bool @@ -24,13 +23,23 @@ Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IVisualDiagnos Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.HotReloadDiagnosticManager(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticsRefresher! diagnosticsRefresher) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Providers.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Register(System.Collections.Generic.IEnumerable! providers) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.RequestRefresh() -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticManager.Unregister(System.Collections.Generic.IEnumerable! providers) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetDiagnosticsAsync(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetDocumentIdentifier() -> Roslyn.LanguageServer.Protocol.TextDocumentIdentifier? +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetId() -> Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.ProjectOrDocumentId +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.GetProject() -> Microsoft.CodeAnalysis.Project! +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.HotReloadDiagnosticSource(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticSource! source, Microsoft.CodeAnalysis.TextDocument! textDocument) -> void +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.IsLiveSource() -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSource.ToDisplayString() -> string! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.CreateDiagnosticSourcesAsync(Microsoft.CodeAnalysis.LanguageServer.Handler.RequestContext context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask> Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider.DocumentHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.HotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager, bool isDocument) -> void -Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.IsDocument.get -> bool +Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.Name.get -> string! Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Internal.HotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider.WorkspaceHotReloadDiagnosticSourceProvider(Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.Contracts.IHotReloadDiagnosticManager! diagnosticManager) -> void Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics.RunningProcessEntry.RunningProcessEntry() -> void From 845f1e3f39952db75e1902897b55486a2ba06f94 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 11:48:25 -0700 Subject: [PATCH 278/292] Inline --- .../Solution/DocumentState_LinkedFileReuse.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 9353d1f140a87..fea908b2679fc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -55,8 +55,10 @@ public DocumentState UpdateTextAndTreeContents( var textAndVersionSource = this.TextAndVersionSource; var treeSource = this.TreeSource; - var newTreeSource = GetReuseTreeSource( - filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer); + var newTreeSource = AsyncLazy.Create( + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); return new DocumentState( languageServices, @@ -67,22 +69,6 @@ public DocumentState UpdateTextAndTreeContents( LoadTextOptions, newTreeSource); - static AsyncLazy GetReuseTreeSource( - string filePath, - LanguageServices languageServices, - LoadTextOptions loadTextOptions, - ParseOptions parseOptions, - AsyncLazy treeSource, - ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, - bool forceEvenIfTreesWouldDiffer) - { - return AsyncLazy.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - } - static bool TryReuseSiblingRoot( string filePath, LanguageServices languageServices, From 7b226e9ad9a152d00169128e715d3fa0c40dabf0 Mon Sep 17 00:00:00 2001 From: Evgeny Tvorun Date: Thu, 25 Apr 2024 11:51:50 -0700 Subject: [PATCH 279/292] Remove unneded docs --- .../DiagnosticSourceProviders/DiagnosticSourceManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs index 6058141a7ec4f..1b870ac2dc45f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/DiagnosticSourceManager.cs @@ -45,11 +45,9 @@ public DiagnosticSourceManager([ImportMany] IEnumerable kvp.Name, kvp => kvp); } - /// public ImmutableArray GetDocumentSourceProviderNames() => _nameToDocumentProviderMap.Keys.ToImmutableArray(); - /// public ImmutableArray GetWorkspaceSourceProviderNames() => _nameToWorkspaceProviderMap.Keys.ToImmutableArray(); From 3dba8a015d7042ce3dd6df6109507700129222ba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 12:13:13 -0700 Subject: [PATCH 280/292] in progress --- .../Workspace/Solution/DocumentState.cs | 28 ++++---- .../Solution/DocumentState_LinkedFileReuse.cs | 64 +++++++++++++------ .../Solution/SourceGeneratedDocumentState.cs | 2 +- .../AsyncLazyTreeAndVersionSource.cs | 44 +++++++++++++ .../ITextAndVersionSource.cs | 0 .../VersionSource/ITreeAndVersionSource.cs | 20 ++++++ .../LoadableTextAndVersionSource.cs | 0 ...coverableTextAndVersion.RecoverableText.cs | 0 .../RecoverableTextAndVersion.cs | 0 9 files changed, 125 insertions(+), 33 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs rename src/Workspaces/Core/Portable/Workspace/Solution/{ => VersionSource}/ITextAndVersionSource.cs (100%) create mode 100644 src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs rename src/Workspaces/Core/Portable/Workspace/Solution/{ => VersionSource}/LoadableTextAndVersionSource.cs (100%) rename src/Workspaces/Core/Portable/Workspace/Solution/{ => VersionSource}/RecoverableTextAndVersion.RecoverableText.cs (100%) rename src/Workspaces/Core/Portable/Workspace/Solution/{ => VersionSource}/RecoverableTextAndVersion.cs (100%) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 9c92c71478ee4..8a4ca25d9b8b0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -31,7 +31,7 @@ internal partial class DocumentState : TextDocumentState private readonly ParseOptions? _options; // null if the document doesn't support syntax trees: - private readonly AsyncLazy? _treeSource; + private readonly ITreeAndVersionSource? _treeSource; protected DocumentState( LanguageServices languageServices, @@ -40,7 +40,7 @@ protected DocumentState( ParseOptions? options, ITextAndVersionSource textSource, LoadTextOptions loadTextOptions, - AsyncLazy? treeSource) + ITreeAndVersionSource? treeSource) : base(languageServices.SolutionServices, documentServiceProvider, attributes, textSource, loadTextOptions) { Contract.ThrowIfFalse(_options is null == _treeSource is null); @@ -79,7 +79,7 @@ public DocumentState( } } - public AsyncLazy? TreeSource => _treeSource; + public ITreeAndVersionSource? TreeSource => _treeSource; [MemberNotNullWhen(true, nameof(_treeSource))] [MemberNotNullWhen(true, nameof(TreeSource))] @@ -97,7 +97,7 @@ public SourceCodeKind SourceCodeKind public bool IsGenerated => Attributes.IsGenerated; - protected static AsyncLazy CreateLazyFullyParsedTree( + protected static ITreeAndVersionSource CreateLazyFullyParsedTree( ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, string? filePath, @@ -105,7 +105,7 @@ protected static AsyncLazy CreateLazyFullyParsedTree( LanguageServices languageServices, PreservationMode mode = PreservationMode.PreserveValue) { - return AsyncLazy.Create( + return AsyncLazyTreeAndVersionSource.Create( static (arg, c) => FullyParseTreeAsync(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), static (arg, c) => FullyParseTree(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), arg: (newTextSource, loadTextOptions, filePath, options, languageServices, mode)); @@ -163,19 +163,19 @@ private static TreeAndVersion CreateTreeAndVersion( return new TreeAndVersion(tree, textAndVersion.Version); } - private static AsyncLazy CreateLazyIncrementallyParsedTree( - AsyncLazy oldTreeSource, + private static ITreeAndVersionSource CreateLazyIncrementallyParsedTree( + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions) { - return AsyncLazy.Create( + return AsyncLazyTreeAndVersionSource.Create( static (arg, c) => IncrementallyParseTreeAsync(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), static (arg, c) => IncrementallyParseTree(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), arg: (oldTreeSource, newTextSource, loadTextOptions)); } private static async Task IncrementallyParseTreeAsync( - AsyncLazy oldTreeSource, + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, CancellationToken cancellationToken) @@ -197,7 +197,7 @@ private static async Task IncrementallyParseTreeAsync( } private static TreeAndVersion IncrementallyParseTree( - AsyncLazy oldTreeSource, + ITreeAndVersionSource oldTreeSource, ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions, CancellationToken cancellationToken) @@ -346,7 +346,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso throw new InvalidOperationException(); } - AsyncLazy? newTreeSource = null; + ITreeAndVersionSource? newTreeSource = null; // Optimization: if we are only changing preprocessor directives, and we've already parsed the existing tree // and it didn't have any, we can avoid a reparse since the tree will be parsed the same. @@ -368,7 +368,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso } if (newTree is not null) - newTreeSource = AsyncLazy.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); + newTreeSource = AsyncLazyTreeAndVersionSource.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); } // If we weren't able to reuse in a smart way, just reparse @@ -457,7 +457,7 @@ public DocumentState UpdateFilePath(string? filePath) protected override TextDocumentState UpdateText(ITextAndVersionSource newTextSource, PreservationMode mode, bool incremental) { - AsyncLazy? newTreeSource; + ITreeAndVersionSource? newTreeSource; if (!SupportsSyntaxTree) { @@ -528,7 +528,7 @@ internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) _options, textSource: text, LoadTextOptions, - treeSource: AsyncLazy.Create(treeAndVersion)); + treeSource: AsyncLazyTreeAndVersionSource.Create(treeAndVersion)); // use static method so we don't capture references to this static (ITextAndVersionSource, TreeAndVersion) CreateTreeWithLazyText( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index fea908b2679fc..3a54b69e7cec0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -13,6 +13,26 @@ namespace Microsoft.CodeAnalysis; internal partial class DocumentState { + private sealed class LinkedFileTreeAndVersionSource( + ITextAndVersionSource siblingTextSource, + ITreeAndVersionSource siblingTreeSource, + bool forceEvenIfTreesWouldDiffer, + AsyncLazy lazyComputation) : ITreeAndVersionSource + { + public readonly ITextAndVersionSource SiblingTextSource = siblingTextSource; + public readonly ITreeAndVersionSource SiblingTreeSource = siblingTreeSource; + public readonly bool ForceEvenIfTreesWouldDiffer = forceEvenIfTreesWouldDiffer; + + public Task GetValueAsync(CancellationToken cancellationToken) + => lazyComputation.GetValueAsync(cancellationToken); + + public TreeAndVersion GetValue(CancellationToken cancellationToken) + => lazyComputation.GetValue(cancellationToken); + + public bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value) + => lazyComputation.TryGetValue(out value); + } + /// /// Returns a new instance of this document state that points to as the /// text contents of the document, and which will produce a syntax tree that reuses from public DocumentState UpdateTextAndTreeContents( ITextAndVersionSource siblingTextSource, - AsyncLazy? siblingTreeSource, + ITreeAndVersionSource? siblingTreeSource, bool forceEvenIfTreesWouldDiffer) { if (!SupportsSyntaxTree) @@ -38,6 +58,17 @@ public DocumentState UpdateTextAndTreeContents( Contract.ThrowIfNull(siblingTreeSource); + // We don't want to point at a long chain of linked files, deferring to each next link of the chain to + // potentially do the work (or potentially failing out). So, if we're about to point at a linked file which is + // already linked to some other file, just point directly at that file instead. This can help when there are + // pathological cases (like a large solution with a single file linked into every project in the solution). + while (siblingTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource && + linkedFileTreeAndVersionSource.SiblingTextSource == siblingTextSource && + linkedFileTreeAndVersionSource.ForceEvenIfTreesWouldDiffer == forceEvenIfTreesWouldDiffer) + { + siblingTreeSource = linkedFileTreeAndVersionSource.SiblingTreeSource; + } + // Always pass along the sibling text. We will always be in sync with that. // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as @@ -46,22 +77,19 @@ public DocumentState UpdateTextAndTreeContents( // invariant that each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers // to the provided source, gets the tree from it, and then wraps its root in a new tree for us. - // copy data from this entity, and pass to static helper, so we don't keep this green node alive. - - var filePath = this.Attributes.SyntaxTreeFilePath; - var languageServices = this.LanguageServices; - var loadTextOptions = this.LoadTextOptions; - var parseOptions = this.ParseOptions; - var textAndVersionSource = this.TextAndVersionSource; - var treeSource = this.TreeSource; + var lazyComputation = AsyncLazy.Create( + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.TreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.TreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (Attributes.SyntaxTreeFilePath, LanguageServices, LoadTextOptions, ParseOptions, TreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - var newTreeSource = AsyncLazy.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.treeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (filePath, languageServices, loadTextOptions, parseOptions, treeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); + var newTreeSource = new LinkedFileTreeAndVersionSource( + siblingTextSource, + siblingTreeSource, + forceEvenIfTreesWouldDiffer, + lazyComputation); return new DocumentState( - languageServices, + LanguageServices, Services, Attributes, _options, @@ -172,9 +200,9 @@ static async Task TryReuseSiblingTreeAsync( LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, + ITreeAndVersionSource siblingTreeSource, bool forceEvenIfTreesWouldDiffer, CancellationToken cancellationToken) { @@ -195,9 +223,9 @@ static TreeAndVersion TryReuseSiblingTree( LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, ITextAndVersionSource siblingTextSource, - AsyncLazy siblingTreeSource, + ITreeAndVersionSource siblingTreeSource, bool forceEvenIfTreesWouldDiffer, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs index e404b3a91c413..cb1591b0eb256 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs @@ -105,7 +105,7 @@ private SourceGeneratedDocumentState( ITextAndVersionSource textSource, SourceText text, LoadTextOptions loadTextOptions, - AsyncLazy treeSource, + ITreeAndVersionSource treeSource, Lazy lazyContentHash, DateTime generationDateTime) : base(languageServices, documentServiceProvider, attributes, options, textSource, loadTextOptions, treeSource) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs new file mode 100644 index 0000000000000..6780ff809213c --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis; + +/// +/// Simple implementation of backed by an opaque ."/> +/// +internal sealed class AsyncLazyTreeAndVersionSource : ITreeAndVersionSource +{ + private readonly AsyncLazy _source; + + private AsyncLazyTreeAndVersionSource(AsyncLazy source) + { + _source = source; + } + + public Task GetValueAsync(CancellationToken cancellationToken) + => _source.GetValueAsync(cancellationToken); + + public TreeAndVersion GetValue(CancellationToken cancellationToken) + => _source.GetValue(cancellationToken); + + public bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value) + => _source.TryGetValue(out value); + + public static AsyncLazyTreeAndVersionSource Create( + Func> asynchronousComputeFunction, + Func? synchronousComputeFunction, TArg arg) + { + return new(AsyncLazy.Create(asynchronousComputeFunction, synchronousComputeFunction, arg)); + } + + public static AsyncLazyTreeAndVersionSource Create(TreeAndVersion source) + => new(AsyncLazy.Create(source)); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITextAndVersionSource.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/ITextAndVersionSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITextAndVersionSource.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs new file mode 100644 index 0000000000000..75e0d0c670b33 --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/ITreeAndVersionSource.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis; + +/// +/// Similar to , but for trees. Allows hiding (or introspecting) the details of how +/// a tree is created for a particular document. +/// +internal interface ITreeAndVersionSource +{ + Task GetValueAsync(CancellationToken cancellationToken); + TreeAndVersion GetValue(CancellationToken cancellationToken); + bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value); +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/LoadableTextAndVersionSource.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/LoadableTextAndVersionSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/LoadableTextAndVersionSource.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.RecoverableText.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.RecoverableText.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.RecoverableText.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.cs similarity index 100% rename from src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/RecoverableTextAndVersion.cs From f3cc5316d45d772c38587ff3ba27cbf0b1956806 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 12:47:27 -0700 Subject: [PATCH 281/292] Demonstrate stack overflow --- .../CoreTest/SolutionTests/SolutionTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 67e9cabedec10..61fd430076efe 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -5080,5 +5080,36 @@ public async Task TestFrozenPartialSolutionOtherLanguage() var frozenCompilation = await frozenProject.GetCompilationAsync(); Assert.Null(frozenCompilation); } + + [Fact] + public async Task TestLargeLinkedFileChain() + { + using var workspace = CreateWorkspace(); + + var project1 = workspace.CurrentSolution + .AddProject($"Project1", $"Project1", LanguageNames.CSharp) + .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; + var documentId1 = project1.DocumentIds.Single(); + + var project2 = project1.Solution + .AddProject($"Project2", $"Project2", LanguageNames.CSharp) + .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; + var documentId2 = project2.DocumentIds.Single(); + + workspace.SetCurrentSolution( + _ => project2.Solution, + (_, _) => (WorkspaceChangeKind.SolutionAdded, null, null)); + + for (var i = 0; i < 4000; i++) + { + workspace.SetCurrentSolution( + old => old.WithDocumentText(documentId1, SourceText.From($"//{new string('.', i)}//")), + (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1)); + } + + var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); + + var tree = await document2.GetSyntaxTreeAsync(); + } } } From 877ade27cad089c377c09ce966d02e3b552a91f7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 12:57:31 -0700 Subject: [PATCH 282/292] work --- .../Solution/DocumentState_LinkedFileReuse.cs | 25 +++++++------------ .../CoreTest/SolutionTests/SolutionTests.cs | 1 + 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 3a54b69e7cec0..feb856fb3d019 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -14,14 +14,10 @@ namespace Microsoft.CodeAnalysis; internal partial class DocumentState { private sealed class LinkedFileTreeAndVersionSource( - ITextAndVersionSource siblingTextSource, - ITreeAndVersionSource siblingTreeSource, - bool forceEvenIfTreesWouldDiffer, + ITreeAndVersionSource originalTreeSource, AsyncLazy lazyComputation) : ITreeAndVersionSource { - public readonly ITextAndVersionSource SiblingTextSource = siblingTextSource; - public readonly ITreeAndVersionSource SiblingTreeSource = siblingTreeSource; - public readonly bool ForceEvenIfTreesWouldDiffer = forceEvenIfTreesWouldDiffer; + public readonly ITreeAndVersionSource OriginalTreeSource = originalTreeSource; public Task GetValueAsync(CancellationToken cancellationToken) => lazyComputation.GetValueAsync(cancellationToken); @@ -62,11 +58,10 @@ public DocumentState UpdateTextAndTreeContents( // potentially do the work (or potentially failing out). So, if we're about to point at a linked file which is // already linked to some other file, just point directly at that file instead. This can help when there are // pathological cases (like a large solution with a single file linked into every project in the solution). - while (siblingTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource && - linkedFileTreeAndVersionSource.SiblingTextSource == siblingTextSource && - linkedFileTreeAndVersionSource.ForceEvenIfTreesWouldDiffer == forceEvenIfTreesWouldDiffer) + var originalTreeSource = this.TreeSource; + while (originalTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource) { - siblingTreeSource = linkedFileTreeAndVersionSource.SiblingTreeSource; + originalTreeSource = linkedFileTreeAndVersionSource.OriginalTreeSource; } // Always pass along the sibling text. We will always be in sync with that. @@ -78,14 +73,12 @@ public DocumentState UpdateTextAndTreeContents( // to the provided source, gets the tree from it, and then wraps its root in a new tree for us. var lazyComputation = AsyncLazy.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.TreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.TreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (Attributes.SyntaxTreeFilePath, LanguageServices, LoadTextOptions, ParseOptions, TreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.SyntaxTreeFilePath, arg.LanguageServices, arg.LoadTextOptions, arg.ParseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (Attributes.SyntaxTreeFilePath, LanguageServices, LoadTextOptions, ParseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); var newTreeSource = new LinkedFileTreeAndVersionSource( - siblingTextSource, - siblingTreeSource, - forceEvenIfTreesWouldDiffer, + originalTreeSource, lazyComputation); return new DocumentState( diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 61fd430076efe..f75eb77eeb55a 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -5110,6 +5110,7 @@ public async Task TestLargeLinkedFileChain() var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); var tree = await document2.GetSyntaxTreeAsync(); + Console.WriteLine(tree); } } } From 68294c12991463fb66dc355dbdc8e7e5446d52f6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:08:52 -0700 Subject: [PATCH 283/292] UPdate test --- src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 61fd430076efe..1306270e3e31f 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -5088,11 +5088,13 @@ public async Task TestLargeLinkedFileChain() var project1 = workspace.CurrentSolution .AddProject($"Project1", $"Project1", LanguageNames.CSharp) + .WithParseOptions(CSharpParseOptions.Default.WithPreprocessorSymbols("DEBUG")) .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; var documentId1 = project1.DocumentIds.Single(); var project2 = project1.Solution .AddProject($"Project2", $"Project2", LanguageNames.CSharp) + .WithParseOptions(CSharpParseOptions.Default.WithPreprocessorSymbols("RELEASE")) .AddDocument($"Document", SourceText.From("class C { }"), filePath: @"c:\test\Document.cs").Project; var documentId2 = project2.DocumentIds.Single(); @@ -5103,13 +5105,17 @@ public async Task TestLargeLinkedFileChain() for (var i = 0; i < 4000; i++) { workspace.SetCurrentSolution( - old => old.WithDocumentText(documentId1, SourceText.From($"//{new string('.', i)}//")), + old => old.WithDocumentText(documentId1, SourceText.From($"#if true //{new string('.', i)}//")), (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1)); + + // ensure that the first document is fine, and we're not stack overflowing on it. + var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1); + await document1.GetSyntaxRootAsync(); } var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); - var tree = await document2.GetSyntaxTreeAsync(); + var root = await document2.GetSyntaxRootAsync(); } } } From 10e6859195926af63b7b1b1d5c8334557e63d468 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:34:22 -0700 Subject: [PATCH 284/292] Rename --- .../Core/Portable/Workspace/Solution/DocumentState.cs | 8 ++++---- ...eAndVersionSource.cs => SimpleTreeAndVersionSource.cs} | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) rename src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/{AsyncLazyTreeAndVersionSource.cs => SimpleTreeAndVersionSource.cs} (82%) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 8a4ca25d9b8b0..8e7daaa323d16 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -105,7 +105,7 @@ protected static ITreeAndVersionSource CreateLazyFullyParsedTree( LanguageServices languageServices, PreservationMode mode = PreservationMode.PreserveValue) { - return AsyncLazyTreeAndVersionSource.Create( + return SimpleTreeAndVersionSource.Create( static (arg, c) => FullyParseTreeAsync(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), static (arg, c) => FullyParseTree(arg.newTextSource, arg.loadTextOptions, arg.filePath, arg.options, arg.languageServices, arg.mode, c), arg: (newTextSource, loadTextOptions, filePath, options, languageServices, mode)); @@ -168,7 +168,7 @@ private static ITreeAndVersionSource CreateLazyIncrementallyParsedTree( ITextAndVersionSource newTextSource, LoadTextOptions loadTextOptions) { - return AsyncLazyTreeAndVersionSource.Create( + return SimpleTreeAndVersionSource.Create( static (arg, c) => IncrementallyParseTreeAsync(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), static (arg, c) => IncrementallyParseTree(arg.oldTreeSource, arg.newTextSource, arg.loadTextOptions, c), arg: (oldTreeSource, newTextSource, loadTextOptions)); @@ -368,7 +368,7 @@ private DocumentState SetParseOptions(ParseOptions options, bool onlyPreprocesso } if (newTree is not null) - newTreeSource = AsyncLazyTreeAndVersionSource.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); + newTreeSource = SimpleTreeAndVersionSource.Create(new TreeAndVersion(newTree, existingTreeAndVersion.Version)); } // If we weren't able to reuse in a smart way, just reparse @@ -528,7 +528,7 @@ internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode) _options, textSource: text, LoadTextOptions, - treeSource: AsyncLazyTreeAndVersionSource.Create(treeAndVersion)); + treeSource: SimpleTreeAndVersionSource.Create(treeAndVersion)); // use static method so we don't capture references to this static (ITextAndVersionSource, TreeAndVersion) CreateTreeWithLazyText( diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs similarity index 82% rename from src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs rename to src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs index 6780ff809213c..46be64b1bd3ec 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/AsyncLazyTreeAndVersionSource.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/VersionSource/SimpleTreeAndVersionSource.cs @@ -14,11 +14,11 @@ namespace Microsoft.CodeAnalysis; /// Simple implementation of backed by an opaque ."/> /// -internal sealed class AsyncLazyTreeAndVersionSource : ITreeAndVersionSource +internal sealed class SimpleTreeAndVersionSource : ITreeAndVersionSource { private readonly AsyncLazy _source; - private AsyncLazyTreeAndVersionSource(AsyncLazy source) + private SimpleTreeAndVersionSource(AsyncLazy source) { _source = source; } @@ -32,13 +32,13 @@ public TreeAndVersion GetValue(CancellationToken cancellationToken) public bool TryGetValue([NotNullWhen(true)] out TreeAndVersion? value) => _source.TryGetValue(out value); - public static AsyncLazyTreeAndVersionSource Create( + public static SimpleTreeAndVersionSource Create( Func> asynchronousComputeFunction, Func? synchronousComputeFunction, TArg arg) { return new(AsyncLazy.Create(asynchronousComputeFunction, synchronousComputeFunction, arg)); } - public static AsyncLazyTreeAndVersionSource Create(TreeAndVersion source) + public static SimpleTreeAndVersionSource Create(TreeAndVersion source) => new(AsyncLazy.Create(source)); } From edcebb9e15db4612aec2670fb5f9d352b6bed8bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:35:05 -0700 Subject: [PATCH 285/292] docs --- .../Workspace/Solution/DocumentState_LinkedFileReuse.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index a134e88a53a9a..0d6e378026426 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -16,13 +16,13 @@ internal partial class DocumentState /// /// when we're linked to another file. This allows us to defer to the linked /// file to get the actual root. Note: we won't know if we can actually use the contents of that linked file until - /// we actually go and realize it. And if we are unable to use it, we will do a normal incremental parse on - /// ourselves and the *text contents* of the linked file. + /// we actually go and realize it. And if we are unable to use it, we will do a normal incremental parse on our own + /// tree and the *text contents* of the linked file. /// /// /// This holds onto our original tree source so that if we're continually overwritten (say because our linked file /// keeps getting edited) that we don't form a long chain of links we have to walk in the case where we do *not* - /// reused the contents of hte linked file. In the case where we do not reuse, we will just do a normal incremental + /// reused the contents of the linked file. In the case where we do not reuse, we will just do a normal incremental /// parse and we don't want a chain of those. Instead, we'll have our last tree, and the latest linked-file text /// and can incrementally parse between those two. /// From ee651b24e75db053ecf307179cf3c8cc08345d11 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:37:29 -0700 Subject: [PATCH 286/292] Revert --- .../Solution/DocumentState_LinkedFileReuse.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 0d6e378026426..88272a4f0cedc 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -106,9 +106,9 @@ private static DocumentState UpdateTextAndTreeContentsWorker( // the tree is never actually needed. var lazyComputation = AsyncLazy.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.attributes, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.attributes, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (attributes, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (filePath: attributes.SyntaxTreeFilePath, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); var newTreeSource = new LinkedFileTreeAndVersionSource( originalTreeSource, @@ -124,7 +124,7 @@ private static DocumentState UpdateTextAndTreeContentsWorker( newTreeSource); static bool TryReuseSiblingRoot( - DocumentInfo.DocumentAttributes attributes, + string filePath, LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, @@ -149,7 +149,7 @@ static bool TryReuseSiblingRoot( // a property of the file, and that should stay the same even if linked into multiple projects. var newTree = treeFactory.CreateSyntaxTree( - attributes.SyntaxTreeFilePath, + filePath, parseOptions, siblingTree.Encoding, loadTextOptions.ChecksumAlgorithm, @@ -222,7 +222,7 @@ bool CanReuseSiblingRoot(bool forceEvenIfTreesWouldDiffer) } static async Task TryReuseSiblingTreeAsync( - DocumentInfo.DocumentAttributes attributes, + string filePath, LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, @@ -237,7 +237,7 @@ static async Task TryReuseSiblingTreeAsync( var siblingRoot = await siblingTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - if (TryReuseSiblingRoot(attributes, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, forceEvenIfTreesWouldDiffer, out var newTreeAndVersion)) + if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, forceEvenIfTreesWouldDiffer, out var newTreeAndVersion)) return newTreeAndVersion; // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. @@ -245,7 +245,7 @@ static async Task TryReuseSiblingTreeAsync( } static TreeAndVersion TryReuseSiblingTree( - DocumentInfo.DocumentAttributes attributes, + string filePath, LanguageServices languageServices, LoadTextOptions loadTextOptions, ParseOptions parseOptions, @@ -260,7 +260,7 @@ static TreeAndVersion TryReuseSiblingTree( var siblingRoot = siblingTree.GetRoot(cancellationToken); - if (TryReuseSiblingRoot(attributes, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, forceEvenIfTreesWouldDiffer, out var newTreeAndVersion)) + if (TryReuseSiblingRoot(filePath, languageServices, loadTextOptions, parseOptions, siblingRoot, siblingTreeAndVersion.Version, forceEvenIfTreesWouldDiffer, out var newTreeAndVersion)) return newTreeAndVersion; // Couldn't use the sibling file to get the tree contents. Instead, incrementally parse our tree to the text passed in. From 23af9de4b54cbd8012f38b8945823e25ae081c89 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:38:46 -0700 Subject: [PATCH 287/292] Simplify --- .../Workspace/Solution/DocumentState_LinkedFileReuse.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 88272a4f0cedc..10c4a58007995 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -115,13 +115,7 @@ private static DocumentState UpdateTextAndTreeContentsWorker( lazyComputation); return new DocumentState( - languageServices, - services, - attributes, - parseOptions, - siblingTextSource, - loadTextOptions, - newTreeSource); + languageServices, services, attributes, parseOptions, siblingTextSource, loadTextOptions, newTreeSource); static bool TryReuseSiblingRoot( string filePath, From ea3c5ff78f267ba5f526754a5ee1300d0e454cec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 13:53:27 -0700 Subject: [PATCH 288/292] Enhance test --- .../CoreTest/SolutionTests/SolutionTests.cs | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index f66e38960dd89..2157655393329 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -5081,8 +5081,12 @@ public async Task TestFrozenPartialSolutionOtherLanguage() Assert.Null(frozenCompilation); } - [Fact] - public async Task TestLargeLinkedFileChain() + [Theory] + [InlineData(1000)] + [InlineData(2000)] + [InlineData(4000)] + [InlineData(8000)] + public async Task TestLargeLinkedFileChain(int intermediatePullCount) { using var workspace = CreateWorkspace(); @@ -5106,24 +5110,31 @@ public async Task TestLargeLinkedFileChain() _ => project2.Solution, (_, _) => (WorkspaceChangeKind.SolutionAdded, null, null)); - var lastContents = ""; - for (var i = 0; i < 4000; i++) + for (var i = 1; i <= 8000; i++) { - lastContents = $"#if true //{new string('.', i)}//"; + var lastContents = $"#if true //{new string('.', i)}//"; workspace.SetCurrentSolution( old => old.WithDocumentText(documentId1, SourceText.From(lastContents)), (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1)); - // ensure that the first document is fine, and we're not stack overflowing on it. - var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1); - await document1.GetSyntaxRootAsync(); - } + // ensure that the first document is fine, and we're not stack overflowing on it. Do this on a disparate + // cadence from our pulls of the second document to ensure we are testing the case where we haven't + // necessarily immediately pulled on hte first doc before pulling on the second. + if (i % 33 == 0) + { + var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1); + await document1.GetSyntaxRootAsync(); + } - var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); + if (i % intermediatePullCount == 0) + { + var document2 = workspace.CurrentSolution.GetRequiredDocument(documentId2); - // Getting the second document should both be fine, and have contents equivalent to what is in the first document. - var root = await document2.GetSyntaxRootAsync(); - Assert.Equal(lastContents, root.ToFullString()); + // Getting the second document should both be fine, and have contents equivalent to what is in the first document. + var root = await document2.GetSyntaxRootAsync(); + Assert.Equal(lastContents, root.ToFullString()); + } + } } } } From d2d0da95f80dc696ec90c80dea8f89b818f39e1b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 14:01:05 -0700 Subject: [PATCH 289/292] Look at one link in the chain --- .../Solution/DocumentState_LinkedFileReuse.cs | 15 +++++++++++++-- .../CoreTest/SolutionTests/SolutionTests.cs | 7 ++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index 10c4a58007995..e764b133c6e64 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -70,9 +70,11 @@ public DocumentState UpdateTextAndTreeContents( // We don't want to point at a long chain of linked files, deferring to each next link of the chain to // potentially do the work (or potentially failing out). So, if we're about to point at a linked file which is // already linked to some other file, just point directly at that file instead. This can help when there are - // pathological cases (like a large solution with a single file linked into every project in the solution). + // pathological cases (like a large solution with a single file linked into every project in the solution). We + // only need to look one deep here as we'll pull that tree source forward to our level. If another link is + // later added to us, it will do the same thing. var originalTreeSource = this.TreeSource; - while (originalTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource) + if (originalTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource) originalTreeSource = linkedFileTreeAndVersionSource.OriginalTreeSource; // Always pass along the sibling text. We will always be in sync with that. @@ -105,6 +107,7 @@ private static DocumentState UpdateTextAndTreeContentsWorker( // that we don't actually realize the tree until we need it. This way we can avoid doing extra work if // the tree is never actually needed. +#if true var lazyComputation = AsyncLazy.Create( static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), @@ -114,6 +117,14 @@ private static DocumentState UpdateTextAndTreeContentsWorker( originalTreeSource, lazyComputation); +#else + var newTreeSource = SimpleTreeAndVersionSource.Create( + static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), + arg: (filePath: attributes.SyntaxTreeFilePath, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); + +#endif + return new DocumentState( languageServices, services, attributes, parseOptions, siblingTextSource, loadTextOptions, newTreeSource); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 2157655393329..ba14298ac442e 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -5117,9 +5117,10 @@ public async Task TestLargeLinkedFileChain(int intermediatePullCount) old => old.WithDocumentText(documentId1, SourceText.From(lastContents)), (_, _) => (WorkspaceChangeKind.DocumentChanged, documentId1.ProjectId, documentId1)); - // ensure that the first document is fine, and we're not stack overflowing on it. Do this on a disparate - // cadence from our pulls of the second document to ensure we are testing the case where we haven't - // necessarily immediately pulled on hte first doc before pulling on the second. + // Ensure that the first document is fine, and we're not stack overflowing on simply getting the tree + // from it. Do this on a disparate cadence from our pulls of the second document to ensure we are + // testing the case where we haven't necessarily immediately pulled on hte first doc before pulling on + // the second. if (i % 33 == 0) { var document1 = workspace.CurrentSolution.GetRequiredDocument(documentId1); From 7b33c3d8a1b54a0962e17b05ae8a5d25a28576c5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 14:12:15 -0700 Subject: [PATCH 290/292] Delete --- .../Workspace/Solution/DocumentState_LinkedFileReuse.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index e764b133c6e64..f553580c59f03 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -107,7 +107,6 @@ private static DocumentState UpdateTextAndTreeContentsWorker( // that we don't actually realize the tree until we need it. This way we can avoid doing extra work if // the tree is never actually needed. -#if true var lazyComputation = AsyncLazy.Create( static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), @@ -117,14 +116,6 @@ private static DocumentState UpdateTextAndTreeContentsWorker( originalTreeSource, lazyComputation); -#else - var newTreeSource = SimpleTreeAndVersionSource.Create( - static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), - arg: (filePath: attributes.SyntaxTreeFilePath, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - -#endif - return new DocumentState( languageServices, services, attributes, parseOptions, siblingTextSource, loadTextOptions, newTreeSource); From c943a140f294139757820ae4b4ca7ce60cc4953d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 25 Apr 2024 14:49:12 -0700 Subject: [PATCH 291/292] Docs --- .../Solution/DocumentState_LinkedFileReuse.cs | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs index f553580c59f03..8c481c1b4a3be 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_LinkedFileReuse.cs @@ -14,19 +14,13 @@ namespace Microsoft.CodeAnalysis; internal partial class DocumentState { /// - /// when we're linked to another file. This allows us to defer to the linked - /// file to get the actual root. Note: we won't know if we can actually use the contents of that linked file until - /// we actually go and realize it. And if we are unable to use it, we will do a normal incremental parse on our own - /// tree and the *text contents* of the linked file. + /// when we're linked to another file (a 'sibling') and will attempt to reuse + /// that sibling's tree as our own. Note: we won't know if we can actually use the contents of that sibling file + /// until we actually go and realize it, as it may contains constructs (like pp-directives) that prevent use. In + /// that case, we'll fall back to a normal incremental parse between our original and the latest text contents of our sibling's file. /// - /// - /// This holds onto our original tree source so that if we're continually overwritten (say because our linked file - /// keeps getting edited) that we don't form a long chain of links we have to walk in the case where we do *not* - /// reused the contents of the linked file. In the case where we do not reuse, we will just do a normal incremental - /// parse and we don't want a chain of those. Instead, we'll have our last tree, and the latest linked-file text - /// and can incrementally parse between those two. - /// - private sealed class LinkedFileTreeAndVersionSource( + private sealed class LinkedFileReuseTreeAndVersionSource( ITreeAndVersionSource originalTreeSource, AsyncLazy lazyComputation) : ITreeAndVersionSource { @@ -67,14 +61,15 @@ public DocumentState UpdateTextAndTreeContents( Contract.ThrowIfNull(siblingTreeSource); - // We don't want to point at a long chain of linked files, deferring to each next link of the chain to - // potentially do the work (or potentially failing out). So, if we're about to point at a linked file which is - // already linked to some other file, just point directly at that file instead. This can help when there are - // pathological cases (like a large solution with a single file linked into every project in the solution). We - // only need to look one deep here as we'll pull that tree source forward to our level. If another link is + // We don't want to point at a long chain of transformations as our sibling files change, deferring to each next + // link of the chain to potentially do the work (or potentially failing out). So, if we're about to do this, + // instead return our original tree-source so that in the case we are unable to use the sibling file's root, we + // can do a single step incremental parse between our original tree and the final sibling text. + // + // We only need to look one deep here as we'll pull that tree source forward to our level. If another link is // later added to us, it will do the same thing. var originalTreeSource = this.TreeSource; - if (originalTreeSource is LinkedFileTreeAndVersionSource linkedFileTreeAndVersionSource) + if (originalTreeSource is LinkedFileReuseTreeAndVersionSource linkedFileTreeAndVersionSource) originalTreeSource = linkedFileTreeAndVersionSource.OriginalTreeSource; // Always pass along the sibling text. We will always be in sync with that. @@ -97,24 +92,18 @@ private static DocumentState UpdateTextAndTreeContentsWorker( ITreeAndVersionSource siblingTreeSource, bool forceEvenIfTreesWouldDiffer) { - // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as - // much memory as possible with linked files. However, we can't point at that source directly. If we did, - // we'd produce the *exact* same tree-reference as another file. That would be bad as it would break the - // invariant that each document gets a unique SyntaxTree. So, instead, we produce a ValueSource that defers - // to the provided source, gets the tree from it, and then wraps its root in a new tree for us. - - // We're going to defer to the sibling tree source to get the tree if possible. We'll do this lazily so - // that we don't actually realize the tree until we need it. This way we can avoid doing extra work if - // the tree is never actually needed. + // if a sibling tree source is provided, then we'll want to attempt to use the tree it creates, to share as much + // memory as possible with linked files. However, we can't point at that source directly. If we did, we'd + // produce the *exact* same tree-reference as another file. That would be bad as it would break the invariant + // that each document gets a unique SyntaxTree. So, instead, we produce a tree-source that defers to the + // provided source, gets the tree from it, and then wraps its root in a new tree for us. var lazyComputation = AsyncLazy.Create( static (arg, cancellationToken) => TryReuseSiblingTreeAsync(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), static (arg, cancellationToken) => TryReuseSiblingTree(arg.filePath, arg.languageServices, arg.loadTextOptions, arg.parseOptions, arg.originalTreeSource, arg.siblingTextSource, arg.siblingTreeSource, arg.forceEvenIfTreesWouldDiffer, cancellationToken), arg: (filePath: attributes.SyntaxTreeFilePath, languageServices, loadTextOptions, parseOptions, originalTreeSource, siblingTextSource, siblingTreeSource, forceEvenIfTreesWouldDiffer)); - var newTreeSource = new LinkedFileTreeAndVersionSource( - originalTreeSource, - lazyComputation); + var newTreeSource = new LinkedFileReuseTreeAndVersionSource(originalTreeSource, lazyComputation); return new DocumentState( languageServices, services, attributes, parseOptions, siblingTextSource, loadTextOptions, newTreeSource); From c67de828b24801f16d3704360c73e073b5eb045d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Thu, 25 Apr 2024 16:02:11 -0700 Subject: [PATCH 292/292] Reset session id in EndSession (#73219) --- .../Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index d0762770efc95..31a5509f51e6f 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -121,6 +121,7 @@ public void EndSession() { Contract.ThrowIfFalse(_sessionId != default, "Session has not started"); _encService.EndDebuggingSession(_sessionId); + _sessionId = default; } internal TestAccessor GetTestAccessor()