diff --git a/.vscode/launch.json b/.vscode/launch.json index ae371f1da31ef..5c446623b4d14 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,10 @@ // If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/artifacts/bin/BuildValidator/Debug/netcoreapp3.1/BuildValidator.dll", "args": [ - "--assembliesPath", "./artifacts/obj/RunTests", + "--assembliesPath", "./artifacts/obj/csc/Debug/netcoreapp3.1", + "--referencesPath", "./artifacts/bin", + "--referencesPath", "C:/Program Files/dotnet/packs/Microsoft.AspNetCore.App.Ref", + "--referencesPath", "C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", "--debugPath", "./artifacts/BuildValidator", "--sourcePath", "." ], diff --git a/Compilers.sln b/Compilers.sln index db88bef2fa1b9..f833a6ddd327c 100644 --- a/Compilers.sln +++ b/Compilers.sln @@ -182,6 +182,7 @@ Global src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{2531a8c4-97dd-47bc-a79c-b7846051e137}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{2531a8c4-97dd-47bc-a79c-b7846051e137}*SharedItemsImports = 5 src\Analyzers\Core\Analyzers\Analyzers.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 + src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 src\Compilers\Core\CommandLine\CommandLine.projitems*{4b45ca0c-03a0-400f-b454-3d4bcb16af38}*SharedItemsImports = 5 diff --git a/azure-pipelines-integration.yml b/azure-pipelines-integration.yml index 6cfb4daece861..54cf4819baf2a 100644 --- a/azure-pipelines-integration.yml +++ b/azure-pipelines-integration.yml @@ -18,10 +18,7 @@ jobs: - job: VS_Integration pool: name: NetCorePublic-Pool - queue: BuildPool.Windows.VS2019.Pre.Scouting.Open -# One this integration test fix has flowed in to master-vs-deps, we can update the queueName variable, and revert this change. -# Tracked by https://github.com/dotnet/roslyn/issues/51312 -# queue: $(queueName) + queue: $(queueName) strategy: maxParallel: 4 matrix: diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index 3f27b27861f96..4b6a45dc352d7 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -26,18 +26,26 @@ variables: - group: DotNet-GitHub-Versions-Repo-Write - name: _DevDivDropAccessToken value: $(System.AccessToken) + # For publishing from devdiv pipeline to devdiv feed, the API key can + # be any non-empty string as authentication is done in the pipeline. + - name: _DevDivNugetFeedAccessToken + value: "AzureArtifacts" # If the pipeline is running in dnceng: - # Get access token with $dn-bot-devdiv-drop-rw-code-rw from DotNet-VSTS-Infra-Access + # Get access token with $dn-bot-devdiv-drop-rw-code-rw and dn-bot-dnceng-build-rw-code-rw from DotNet-VSTS-Infra-Access # Get $dotnetfeed-storage-access-key-1 from DotNet-Blob-Feed # Get $microsoft-symbol-server-pat and $symweb-symbol-server-pat from DotNet-Symbol-Server-Pats # Get $AccessToken-dotnet-build-bot-public-repo from DotNet-Versions-Publish + # Get $dn-bot-devdiv-packaging-rw from DotNet-DevDiv-Insertion-Workflow-Variables - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - group: DotNet-Blob-Feed - group: DotNet-Symbol-Server-Pats - group: DotNet-Versions-Publish - group: DotNet-VSTS-Infra-Access + - group: DotNet-DevDiv-Insertion-Workflow-Variables - name: _DevDivDropAccessToken value: $(dn-bot-devdiv-drop-rw-code-rw) + - name: _DevDivNugetFeedAccessToken + value: $(dn-bot-devdiv-packaging-rw) stages: - stage: build @@ -109,7 +117,11 @@ stages: - task: PowerShell@2 displayName: Merge master-vs-deps into source branch inputs: - filePath: 'scripts\merge-vs-deps.ps1' + filePath: 'scripts\merge-vs-deps.ps1' + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + arguments: '-userName "$(Build.RequestedFor)" -userEmail $(Build.RequestedForEmail)' + ${{ if eq(variables['System.TeamProject'], 'internal') }}: + arguments: '-userName "$(Build.RequestedFor)" -userEmail $(Build.RequestedForEmail) -accessToken $(dn-bot-dnceng-build-rw-code-rw)' condition: and(succeeded(), eq(variables['SourceBranchName'], 'master')) - powershell: Write-Host "##vso[task.setvariable variable=VisualStudio.DropName]Products/$(System.TeamProject)/$(Build.Repository.Name)/$(SourceBranchName)/$(Build.BuildNumber)" @@ -180,7 +192,7 @@ stages: displayName: Publish Assets inputs: filePath: 'eng\publish-assets.ps1' - arguments: '-configuration $(BuildConfiguration) -branchName "$(SourceBranchName)" -gitHubUserName $(Roslyn.GitHubUserName) -gitHubToken $(AccessToken-dotnet-build-bot-public-repo) -gitHubEmail $(Roslyn.GitHubEmail)' + arguments: '-configuration $(BuildConfiguration) -branchName "$(SourceBranchName)" -gitHubUserName $(Roslyn.GitHubUserName) -gitHubToken $(AccessToken-dotnet-build-bot-public-repo) -gitHubEmail $(Roslyn.GitHubEmail) -devdivApiKey $(_DevDivNugetFeedAccessToken)' condition: and(succeeded(), eq(variables['PRNumber'], 'default')) # Publish OptProf configuration files diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bb28e7fecfffc..3d1e6a024468e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -215,7 +215,8 @@ jobs: - job: Correctness_Rebuild pool: - vmImage: windows-2019 + name: NetCorePublic-Pool + queue: BuildPool.Windows.10.Amd64.Open timeoutInMinutes: 90 steps: - template: eng/pipelines/checkout-windows-task.yml @@ -226,13 +227,7 @@ jobs: filePath: eng/build.ps1 arguments: -configuration Debug -prepareMachine -ci -restore -binaryLog - - task: PowerShell@2 - displayName: Build - inputs: - filePath: eng/build.ps1 - arguments: -configuration Debug -prepareMachine -ci -build -bootstrap -publish -binaryLog -skipDocumentation - - - script: .\artifacts\bin\BuildValidator\Debug\net472\BuildValidator.exe --assembliesPath .\artifacts\obj\Microsoft.CodeAnalysis --debugPath .\artifacts\BuildValidator --sourcePath . + - powershell: .\eng\test-rebuild.ps1 -ci -configuration Release displayName: Run BuildValidator - task: PublishBuildArtifacts@1 diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 5169d06e14f14..e77488bef3d5a 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -8,25 +8,29 @@ efforts behind them. # C# Next -| Feature | Branch | State | Developers | Reviewer | LDM Champ | -|----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------------|-----------------------------------------| -| [Record structs](https://github.com/dotnet/csharplang/issues/4334) | [record-structs](https://github.com/dotnet/roslyn/tree/features/record-structs) | [In Progress](https://github.com/dotnet/roslyn/issues/51199) | [jcouv](https://github.com/jcouv) | TBD | [jcouv](https://github.com/jcouv) | -| [Global Using Directive](https://github.com/dotnet/csharplang/issues/3428) | [GlobalUsingDirective](https://github.com/dotnet/roslyn/tree/features/GlobalUsingDirective) | [In Progress](https://github.com/dotnet/roslyn/issues/51307) | [AlekseyTs](https://github.com/AlekseyTs) | TBD | [AlekseyTs](https://github.com/AlekseyTs) | -| [nameof(parameter)](https://github.com/dotnet/csharplang/issues/373) | master | [In Progress](https://github.com/dotnet/roslyn/issues/40524) | [jcouv](https://github.com/jcouv) | TBD | [jcouv](https://github.com/jcouv) | -| [Relax ordering of `ref` and `partial` modifiers](https://github.com/dotnet/csharplang/issues/946) | [ref-partial](https://github.com/dotnet/roslyn/tree/features/ref-partial) | In Progress | [alrz](https://github.com/alrz) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | -| [Parameter null-checking](https://github.com/dotnet/csharplang/issues/2145) | [param-nullchecking](https://github.com/dotnet/roslyn/tree/features/param-nullchecking) | [In Progress](https://github.com/dotnet/roslyn/issues/36024) | [fayrose](https://github.com/fayrose) | [agocke](https://github.com/agocke) | [jaredpar](https://github.com/jaredpar) | -| [Caller expression attribute](https://github.com/dotnet/csharplang/issues/287) | [caller-expression](https://github.com/dotnet/roslyn/tree/features/caller-expression) | Prototype | [alrz](https://github.com/alrz) | [jcouv](https://github.com/jcouv) | [jcouv](https://github.com/jcouv) | -| [Generic attributes](https://github.com/dotnet/csharplang/issues/124) | [generic-attributes](https://github.com/dotnet/roslyn/tree/features/generic-attributes) | [In Progress](https://github.com/dotnet/roslyn/issues/36285) | [AviAvni](https://github.com/AviAvni) | [agocke](https://github.com/agocke) | [mattwar](https://github.com/mattwar) | -| [Default in deconstruction](https://github.com/dotnet/roslyn/pull/25562) | [decon-default](https://github.com/dotnet/roslyn/tree/features/decon-default) | [Implemented](https://github.com/dotnet/roslyn/issues/25559) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | -| [Constant Interpolated Strings](https://github.com/dotnet/csharplang/issues/2951) | master | [Merged into 16.9p3](https://github.com/dotnet/roslyn/pull/49676) | [kevinsun-dev](https://github.com/kevinsun-dev) | [333fred](https://github.com/333fred) | [jaredar](https://github.com/jaredpar), [agocke](https://github.com/agocke) | -| [Mix declarations and variables in deconstruction](https://github.com/dotnet/csharplang/issues/125) | master | [Merged into 16.10](https://github.com/dotnet/roslyn/issues/47746) | [YairHalberstadt ](https://github.com/YairHalberstadt ) | [jcouv](https://github.com/jcouv) | [MadsTorgersen](https://github.com/MadsTorgersen) | +| Feature | Branch | State | Developers | Reviewer | LDM Champ | +| ------- | ------ | ----- | ---------- | -------- | --------- | +| [Record structs](https://github.com/dotnet/csharplang/issues/4334) | [record-structs](https://github.com/dotnet/roslyn/tree/features/record-structs) | [In Progress](https://github.com/dotnet/roslyn/issues/51199) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv) | +| [Global Using Directive](https://github.com/dotnet/csharplang/issues/3428) | [GlobalUsingDirective](https://github.com/dotnet/roslyn/tree/features/GlobalUsingDirective) | [In Progress](https://github.com/dotnet/roslyn/issues/51307) | [AlekseyTs](https://github.com/AlekseyTs) | [333fred](https://github.com/333fred), [cston](https://github.com/cston) | [AlekseyTs](https://github.com/AlekseyTs) | +| [File-scoped namespace](https://github.com/dotnet/csharplang/issues/137) | [FileScopedNamespaces](https://github.com/dotnet/roslyn/tree/features/FileScopedNamespaces) | In Progress | [chsienki](https://github.com/chsienki) | [cston](https://github.com/cston), [RikkiGibson](https://github.com/RikkiGibson) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | +| [Interpolated string improvements](https://github.com/dotnet/csharplang/issues/2302) | [interpolated-string](https://github.com/dotnet/roslyn/tree/features/interpolated-string) | [In Progress](https://github.com/dotnet/roslyn/issues/51499) | [333fred](https://github.com/333fred) | [AlekseyTs](https://github.com/AlekseyTs), [chsienki](https://github.com/chsienki) | [jaredpar](https://github.com/jaredpar) | +| [Parameterless struct constructors](https://github.com/dotnet/csharplang/issues/99) | TBD | In Progress | [cston](https://github.com/cston) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [jcouv](https://github.com/jouv) | +| [nameof(parameter)](https://github.com/dotnet/csharplang/issues/373) | master | [In Progress](https://github.com/dotnet/roslyn/issues/40524) | [jcouv](https://github.com/jcouv) | TBD | [jcouv](https://github.com/jcouv) | +| [Improved Definite Assignment](https://github.com/dotnet/csharplang/issues/4465) | [improved-definite-assignment](https://github.com/dotnet/roslyn/tree/features/improved-definite-assignment) | [In Progress](https://github.com/dotnet/roslyn/issues/51463) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv) | [jaredpar](https://github.com/jaredpar) | +| [Relax ordering of `ref` and `partial` modifiers](https://github.com/dotnet/csharplang/issues/946) | [ref-partial](https://github.com/dotnet/roslyn/tree/features/ref-partial) | In Progress | [alrz](https://github.com/alrz) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | +| [Parameter null-checking](https://github.com/dotnet/csharplang/issues/2145) | [param-nullchecking](https://github.com/dotnet/roslyn/tree/features/param-nullchecking) | [In Progress](https://github.com/dotnet/roslyn/issues/36024) | [fayrose](https://github.com/fayrose) | [agocke](https://github.com/agocke) | [jaredpar](https://github.com/jaredpar) | +| [Caller expression attribute](https://github.com/dotnet/csharplang/issues/287) | [caller-expression](https://github.com/dotnet/roslyn/tree/features/caller-expression) | Prototype | [alrz](https://github.com/alrz) | [jcouv](https://github.com/jcouv) | [jcouv](https://github.com/jcouv) | +| [Generic attributes](https://github.com/dotnet/csharplang/issues/124) | [generic-attributes](https://github.com/dotnet/roslyn/tree/features/generic-attributes) | [In Progress](https://github.com/dotnet/roslyn/issues/36285) | [AviAvni](https://github.com/AviAvni) | [agocke](https://github.com/agocke) | [mattwar](https://github.com/mattwar) | +| [Default in deconstruction](https://github.com/dotnet/roslyn/pull/25562) | [decon-default](https://github.com/dotnet/roslyn/tree/features/decon-default) | [Implemented](https://github.com/dotnet/roslyn/issues/25559) | [jcouv](https://github.com/jcouv) | [gafter](https://github.com/gafter) | [jcouv](https://github.com/jcouv) | +| [Constant Interpolated Strings](https://github.com/dotnet/csharplang/issues/2951) | master | [Merged into 16.9p3](https://github.com/dotnet/roslyn/pull/49676) | [kevinsun-dev](https://github.com/kevinsun-dev) | [333fred](https://github.com/333fred) | [jaredar](https://github.com/jaredpar), [agocke](https://github.com/agocke) | +| [Mix declarations and variables in deconstruction](https://github.com/dotnet/csharplang/issues/125) | master | [Merged into 16.10](https://github.com/dotnet/roslyn/issues/47746) | [YairHalberstadt ](https://github.com/YairHalberstadt) | [jcouv](https://github.com/jcouv) | [MadsTorgersen](https://github.com/MadsTorgersen) | | [List patterns](https://github.com/dotnet/csharplang/issues/3435) | [list-patterns](https://github.com/dotnet/roslyn/tree/features/list-patterns) | [In Progress](https://github.com/dotnet/roslyn/issues/51289) | [alrz](https://github.com/alrz) | [333fred](https://github.com/333fred) (tentative) | [333fred](https://github.com/333fred) | # VB 16.9 -| Feature | Branch | State | Developers | Reviewer | LDM Champ | -|----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------------------------------------|-----------------------------------------| -| [Enable consumption of init-only properties](https://github.com/dotnet/roslyn/pull/50414) | master | [Merged (16.9p3)](https://github.com/dotnet/roslyn/issues/50792) | [AlekseyTs](https://github.com/AlekseyTs) | [jcouv](https://github.com/jcouv) +| Feature | Branch | State | Developers | Reviewer | LDM Champ | +|-------------------------------------------------------------------------------------------|--------|------------------------------------------------------------------|-------------------------------------------|-----------------------------------|-----------| +| [Enable consumption of init-only properties](https://github.com/dotnet/roslyn/pull/50414) | master | [Merged (16.9p3)](https://github.com/dotnet/roslyn/issues/50792) | [AlekseyTs](https://github.com/AlekseyTs) | [jcouv](https://github.com/jcouv) | N/A | # C# 9 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 148bd1902c0ed..c02e857ee30fd 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -7,9 +7,9 @@ https://github.com/dotnet/arcade 4957a6f3dd5f6fd1ca9ff06f9c50402f2e3b17e8 - + https://github.com/dotnet/roslyn - 69f354dd21df8dac374c35169bdabb9000cfdea3 + 686f13c61b1e26071ae89e107b0a3407a6b93a74 https://github.com/dotnet/arcade diff --git a/eng/Versions.props b/eng/Versions.props index 232567d6adb1d..d4c63bf331387 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -23,7 +23,7 @@ - 3.10.0-1.21113.12 + 3.10.0-2.21122.2 @@ -32,7 +32,8 @@ 1.0.1-beta1.20623.3 3.8.0 16.8.181 - 5.0.0-alpha1.19409.1 + 5.0.0-alpha1.19409.1 + 5.0.0-preview.1.20112.8 16.9.150 16.8.30406.65-pre + + + + + PreserveNewest + runtimes + false + false + + + PreserveNewest + runtimes + false + false + + + PreserveNewest + runtimes + false + false + + + + + + + \ No newline at end of file diff --git a/eng/targets/Imports.targets b/eng/targets/Imports.targets index 15b067deb0476..e33e38e1c82d5 100644 --- a/eng/targets/Imports.targets +++ b/eng/targets/Imports.targets @@ -46,6 +46,15 @@ --> false true + + + false + true - false - enable diff --git a/eng/test-rebuild.ps1 b/eng/test-rebuild.ps1 index 10b83f7ce4a73..5730b9d841f2b 100644 --- a/eng/test-rebuild.ps1 +++ b/eng/test-rebuild.ps1 @@ -6,6 +6,7 @@ param( [string]$configuration = "Debug", [switch]$ci = $false, + [switch]$noBuild = $false, [switch]$help) Set-StrictMode -version 2.0 @@ -15,6 +16,7 @@ function Print-Usage() { Write-Host "Usage: test-rebuild.ps1" Write-Host " -configuration Build configuration ('Debug' or 'Release')" Write-Host " -ci Set when running on CI server" + Write-Host " -noBuild If set, skips running a bootstrap build before running the rebuild" Write-Host " -help Print help and exit" } @@ -27,9 +29,54 @@ try { . (Join-Path $PSScriptRoot "build-utils.ps1") Push-Location $RepoRoot - Write-Host "Building Roslyn" - Exec-Console (Join-Path $PSScriptRoot "build.ps1") "-restore -build -ci:$ci -configuration:$configuration -pack -binaryLog" - Exec-Console "artifacts\bin\BuildValidator\$configuration\net472\BuildValidator.exe" "--assembliesPath '$ArtifactsDir/obj/Microsoft.CodeAnalysis'" + if (-not $noBuild) { + Write-Host "Building Roslyn" + Exec-Block { & (Join-Path $PSScriptRoot "build.ps1") -build -bootstrap -ci:$ci -configuration:$configuration -pack -binaryLog } + } + + $dotnetInstallDir = (InitializeDotNetCli -install:$true) + $rebuildArgs = ("--verbose" + + " --assembliesPath `"$ArtifactsDir/obj/AnalyzerRunner/$configuration/netcoreapp3.1`"" + + " --assembliesPath `"$ArtifactsDir/obj/AnalyzerRunner/$configuration/net5.0`"" + + " --assembliesPath `"$ArtifactsDir/obj/CodeStyleConfigFileGenerator`"" + + " --assembliesPath `"$ArtifactsDir/obj/csc/$configuration/netcoreapp3.1`"" + + " --assembliesPath `"$ArtifactsDir/obj/CSharpResultProvider.NetFX20`"" + + " --assembliesPath `"$ArtifactsDir/obj/CSharpSyntaxGenerator`"" + + " --assembliesPath `"$ArtifactsDir/obj/csi/$configuration/netcoreapp3.1`"" + + " --assembliesPath `"$ArtifactsDir/obj/IdeCoreBenchmarks/$configuration/netcoreapp3.1`"" + + " --assembliesPath `"$ArtifactsDir/obj/IdeCoreBenchmarks/$configuration/net5.0`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.CodeStyle`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.CodeStyle.Fixes`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.Compiler.Test.Resources`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.CSharp`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.CSharp.CodeStyle`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.CSharp.ExpressionCompiler`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.CSharp.Features`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.CSharp.Workspaces`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.EditorFeatures.Text`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.ExpressionCompiler`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.ExternalAccess.Debugger`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.ExternalAccess.Razor`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.Remote.Razor.ServiceHub`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.Remote.Workspaces`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.ResultProvider`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.Scripting`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.Scripting.TestUtilities`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.TestSourceGenerator`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.VisualBasic`"" + + " --assembliesPath `"$ArtifactsDir/obj/Microsoft.CodeAnalysis.Workspaces`"" + + " --assembliesPath `"$ArtifactsDir/obj/PrepareTests`"" + + " --assembliesPath `"$ArtifactsDir/obj/RunTests`"" + + " --assembliesPath `"$ArtifactsDir/obj/vbc/$configuration/netcoreapp3.1`"" + + " --assembliesPath `"$ArtifactsDir/obj/VBCSCompiler/$configuration/netcoreapp3.1`"" + + + " --debugPath `"$ArtifactsDir/BuildValidator`"" + + " --sourcePath `"$RepoRoot`"" + + " --referencesPath `"$ArtifactsDir/bin`"" + + " --referencesPath `"$dotnetInstallDir/packs`"") + Exec-Console "$ArtifactsDir/bin/BuildValidator/$configuration/net472/BuildValidator.exe" $rebuildArgs exit 0 } diff --git a/scripts/merge-vs-deps.ps1 b/scripts/merge-vs-deps.ps1 index ef91539806527..37a9ba2fa61a1 100644 --- a/scripts/merge-vs-deps.ps1 +++ b/scripts/merge-vs-deps.ps1 @@ -1,4 +1,20 @@ -git pull origin master-vs-deps +[CmdletBinding(PositionalBinding=$false)] +Param( + [string]$userName, + [string]$userEmail, + [string]$accessToken = "") + +git config user.name $userName +git config user.email $userEmail + +if ($accessToken -eq "") { + git pull origin master-vs-deps +} +else { + $base64Pat = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":$accessToken")) + git -c http.extraheader="AUTHORIZATION: Basic $base64Pat" pull origin master-vs-deps +} + if (-not $?) { Write-Host "##vso[task.logissue type=error]Failed to merge master-vs-deps into source branch" diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs index 05ed9d38f94c6..d1586fcb68699 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs @@ -155,6 +155,7 @@ private BoundAttribute BindAttributeCore(AttributeSyntax node, NamedTypeSymbol a MethodSymbol? attributeConstructor = null; // Bind attributeType's constructor based on the bound constructor arguments + ImmutableArray boundConstructorArguments; if (!attributeTypeForBinding.IsErrorType()) { attributeConstructor = BindAttributeConstructor(node, @@ -165,8 +166,16 @@ private BoundAttribute BindAttributeCore(AttributeSyntax node, NamedTypeSymbol a suppressErrors: attributeType.IsErrorType(), ref argsToParamsOpt, ref expanded, - ref useSiteInfo); + ref useSiteInfo, + out boundConstructorArguments); } + else + { + boundConstructorArguments = analyzedArguments.ConstructorArguments.Arguments.SelectAsArray( + static (arg, attributeArgumentBinder) => attributeArgumentBinder.BindToTypeForErrorRecovery(arg), + attributeArgumentBinder); + } + Debug.Assert(boundConstructorArguments.All(a => !a.NeedsToBeConverted())); diagnostics.Add(node, useSiteInfo); if (attributeConstructor is object) @@ -179,11 +188,11 @@ private BoundAttribute BindAttributeCore(AttributeSyntax node, NamedTypeSymbol a } } - var constructorArguments = analyzedArguments.ConstructorArguments; - ImmutableArray boundConstructorArguments = constructorArguments.Arguments.ToImmutableAndFree(); - ImmutableArray boundConstructorArgumentNamesOpt = constructorArguments.GetNames(); - ImmutableArray boundNamedArguments = analyzedArguments.NamedArguments; - constructorArguments.Free(); + ImmutableArray boundConstructorArgumentNamesOpt = analyzedArguments.ConstructorArguments.GetNames(); + ImmutableArray boundNamedArguments = analyzedArguments.NamedArguments?.ToImmutableAndFree() ?? ImmutableArray.Empty; + Debug.Assert(boundNamedArguments.All(arg => !arg.Right.NeedsToBeConverted())); + + analyzedArguments.ConstructorArguments.Free(); return new BoundAttribute(node, attributeConstructor, boundConstructorArguments, boundConstructorArgumentNamesOpt, argsToParamsOpt, expanded, boundNamedArguments, resultKind, attributeType, hasErrors: resultKind != LookupResultKind.Viable); @@ -293,7 +302,7 @@ protected bool IsAttributeConditionallyOmitted(NamedTypeSymbol attributeType, Sy } /// - /// The result of this method captures some AnalyzedArguments, which must be free'ed by the caller. + /// The caller is responsible for freeing and . /// private AnalyzedAttributeArguments BindAttributeArguments( AttributeArgumentListSyntax? attributeArgumentList, @@ -301,11 +310,10 @@ private AnalyzedAttributeArguments BindAttributeArguments( BindingDiagnosticBag diagnostics) { var boundConstructorArguments = AnalyzedArguments.GetInstance(); - var boundNamedArguments = ImmutableArray.Empty; + ArrayBuilder? boundNamedArgumentsBuilder = null; if (attributeArgumentList != null) { - ArrayBuilder? boundNamedArgumentsBuilder = null; HashSet? boundNamedArgumentsSet = null; // Only report the first "non-trailing named args required C# 7.2" error, @@ -342,7 +350,7 @@ private AnalyzedAttributeArguments BindAttributeArguments( string argumentName = argument.NameEquals.Name.Identifier.ValueText!; if (boundNamedArgumentsBuilder == null) { - boundNamedArgumentsBuilder = ArrayBuilder.GetInstance(); + boundNamedArgumentsBuilder = ArrayBuilder.GetInstance(); boundNamedArgumentsSet = new HashSet(); } else if (boundNamedArgumentsSet!.Contains(argumentName)) @@ -351,22 +359,17 @@ private AnalyzedAttributeArguments BindAttributeArguments( Error(diagnostics, ErrorCode.ERR_DuplicateNamedAttributeArgument, argument, argumentName); } - BoundExpression boundNamedArgument = BindNamedAttributeArgument(argument, attributeType, diagnostics); + BoundAssignmentOperator boundNamedArgument = BindNamedAttributeArgument(argument, attributeType, diagnostics); boundNamedArgumentsBuilder.Add(boundNamedArgument); boundNamedArgumentsSet.Add(argumentName); } } - - if (boundNamedArgumentsBuilder != null) - { - boundNamedArguments = boundNamedArgumentsBuilder.ToImmutableAndFree(); - } } - return new AnalyzedAttributeArguments(boundConstructorArguments, boundNamedArguments); + return new AnalyzedAttributeArguments(boundConstructorArguments, boundNamedArgumentsBuilder); } - private BoundExpression BindNamedAttributeArgument(AttributeArgumentSyntax namedArgument, NamedTypeSymbol attributeType, BindingDiagnosticBag diagnostics) + private BoundAssignmentOperator BindNamedAttributeArgument(AttributeArgumentSyntax namedArgument, NamedTypeSymbol attributeType, BindingDiagnosticBag diagnostics) { bool wasError; LookupResultKind resultKind; @@ -537,7 +540,8 @@ protected MethodSymbol BindAttributeConstructor( bool suppressErrors, ref ImmutableArray argsToParamsOpt, ref bool expanded, - ref CompoundUseSiteInfo useSiteInfo) + ref CompoundUseSiteInfo useSiteInfo, + out ImmutableArray constructorArguments) { MemberResolutionResult memberResolutionResult; ImmutableArray candidateConstructors; @@ -556,6 +560,11 @@ protected MethodSymbol BindAttributeConstructor( memberResolutionResult.IsValid && !IsConstructorAccessible(memberResolutionResult.Member, ref useSiteInfo) ? LookupResultKind.Inaccessible : LookupResultKind.OverloadResolutionFailure); + constructorArguments = BuildArgumentsForErrorRecovery(boundConstructorArguments, candidateConstructors); + } + else + { + constructorArguments = boundConstructorArguments.Arguments.ToImmutable(); } argsToParamsOpt = memberResolutionResult.Result.ArgsToParamsOpt; expanded = memberResolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; @@ -982,7 +991,7 @@ public ImmutableArray VisitArguments(ImmutableArray> VisitNamedArguments(ImmutableArray arguments, BindingDiagnosticBag diagnostics, ref bool attrHasErrors) + public ImmutableArray> VisitNamedArguments(ImmutableArray arguments, BindingDiagnosticBag diagnostics, ref bool attrHasErrors) { ArrayBuilder>? builder = null; foreach (var argument in arguments) @@ -1008,28 +1017,20 @@ public ImmutableArray> VisitNamedArguments(I return builder.ToImmutableAndFree(); } - private KeyValuePair? VisitNamedArgument(BoundExpression argument, BindingDiagnosticBag diagnostics, ref bool attrHasErrors) + private KeyValuePair? VisitNamedArgument(BoundAssignmentOperator assignment, BindingDiagnosticBag diagnostics, ref bool attrHasErrors) { KeyValuePair? visitedArgument = null; - switch (argument.Kind) + switch (assignment.Left.Kind) { - case BoundKind.AssignmentOperator: - var assignment = (BoundAssignmentOperator)argument; - - switch (assignment.Left.Kind) - { - case BoundKind.FieldAccess: - var fa = (BoundFieldAccess)assignment.Left; - visitedArgument = new KeyValuePair(fa.FieldSymbol.Name, VisitExpression(assignment.Right, diagnostics, ref attrHasErrors, argument.HasAnyErrors)); - break; - - case BoundKind.PropertyAccess: - var pa = (BoundPropertyAccess)assignment.Left; - visitedArgument = new KeyValuePair(pa.PropertySymbol.Name, VisitExpression(assignment.Right, diagnostics, ref attrHasErrors, argument.HasAnyErrors)); - break; - } + case BoundKind.FieldAccess: + var fa = (BoundFieldAccess)assignment.Left; + visitedArgument = new KeyValuePair(fa.FieldSymbol.Name, VisitExpression(assignment.Right, diagnostics, ref attrHasErrors, assignment.HasAnyErrors)); + break; + case BoundKind.PropertyAccess: + var pa = (BoundPropertyAccess)assignment.Left; + visitedArgument = new KeyValuePair(pa.PropertySymbol.Name, VisitExpression(assignment.Right, diagnostics, ref attrHasErrors, assignment.HasAnyErrors)); break; } @@ -1248,9 +1249,9 @@ private static TypedConstant CreateTypedConstant(BoundExpression node, TypedCons private struct AnalyzedAttributeArguments { internal readonly AnalyzedArguments ConstructorArguments; - internal readonly ImmutableArray NamedArguments; + internal readonly ArrayBuilder? NamedArguments; - internal AnalyzedAttributeArguments(AnalyzedArguments constructorArguments, ImmutableArray namedArguments) + internal AnalyzedAttributeArguments(AnalyzedArguments constructorArguments, ArrayBuilder? namedArguments) { this.ConstructorArguments = constructorArguments; this.NamedArguments = namedArguments; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index ef39b472be66e..1ccd722dbbb72 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -59,6 +59,7 @@ private static ImmutableArray GetOriginalMethods(OverloadResolutio return builder.ToImmutableAndFree(); } +#nullable enable /// /// Helper method to create a synthesized method invocation expression. /// @@ -81,7 +82,7 @@ internal BoundExpression MakeInvocationExpression( BindingDiagnosticBag diagnostics, SeparatedSyntaxList typeArgsSyntax = default(SeparatedSyntaxList), ImmutableArray typeArgs = default(ImmutableArray), - CSharpSyntaxNode queryClause = null, + CSharpSyntaxNode? queryClause = null, bool allowFieldsAndProperties = false, bool allowUnexpandedForm = true) { @@ -141,6 +142,7 @@ internal BoundExpression MakeInvocationExpression( analyzedArguments.Free(); return result; } +#nullable disable /// /// Bind an expression as a method invocation. diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 0f9ece1fc4f05..40591d3932cc3 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1615,7 +1615,7 @@ - + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index 8f5be8db406c3..ab4a498be4c80 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -73,7 +73,7 @@ internal partial class BoundAnonymousObjectCreationExpression internal partial class BoundAttribute { - protected override ImmutableArray Children => StaticCast.From(this.ConstructorArguments.AddRange(this.NamedArguments)); + protected override ImmutableArray Children => StaticCast.From(this.ConstructorArguments.AddRange(StaticCast.From(this.NamedArguments))); } internal partial class BoundQueryClause diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 1423b578476ef..680d8ad611fea 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -5756,7 +5756,7 @@ public BoundEventAssignmentOperator Update(EventSymbol @event, bool isAddition, internal sealed partial class BoundAttribute : BoundExpression { - public BoundAttribute(SyntaxNode syntax, MethodSymbol? constructor, ImmutableArray constructorArguments, ImmutableArray constructorArgumentNamesOpt, ImmutableArray constructorArgumentsToParamsOpt, bool constructorExpanded, ImmutableArray namedArguments, LookupResultKind resultKind, TypeSymbol type, bool hasErrors = false) + public BoundAttribute(SyntaxNode syntax, MethodSymbol? constructor, ImmutableArray constructorArguments, ImmutableArray constructorArgumentNamesOpt, ImmutableArray constructorArgumentsToParamsOpt, bool constructorExpanded, ImmutableArray namedArguments, LookupResultKind resultKind, TypeSymbol type, bool hasErrors = false) : base(BoundKind.Attribute, syntax, type, hasErrors || constructorArguments.HasErrors() || namedArguments.HasErrors()) { @@ -5786,14 +5786,14 @@ public BoundAttribute(SyntaxNode syntax, MethodSymbol? constructor, ImmutableArr public bool ConstructorExpanded { get; } - public ImmutableArray NamedArguments { get; } + public ImmutableArray NamedArguments { get; } private readonly LookupResultKind _ResultKind; public override LookupResultKind ResultKind { get { return _ResultKind; } } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitAttribute(this); - public BoundAttribute Update(MethodSymbol? constructor, ImmutableArray constructorArguments, ImmutableArray constructorArgumentNamesOpt, ImmutableArray constructorArgumentsToParamsOpt, bool constructorExpanded, ImmutableArray namedArguments, LookupResultKind resultKind, TypeSymbol type) + public BoundAttribute Update(MethodSymbol? constructor, ImmutableArray constructorArguments, ImmutableArray constructorArgumentNamesOpt, ImmutableArray constructorArgumentsToParamsOpt, bool constructorExpanded, ImmutableArray namedArguments, LookupResultKind resultKind, TypeSymbol type) { if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(constructor, this.Constructor) || constructorArguments != this.ConstructorArguments || constructorArgumentNamesOpt != this.ConstructorArgumentNamesOpt || constructorArgumentsToParamsOpt != this.ConstructorArgumentsToParamsOpt || constructorExpanded != this.ConstructorExpanded || namedArguments != this.NamedArguments || resultKind != this.ResultKind || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { @@ -10526,7 +10526,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitAttribute(BoundAttribute node) { ImmutableArray constructorArguments = this.VisitList(node.ConstructorArguments); - ImmutableArray namedArguments = this.VisitList(node.NamedArguments); + ImmutableArray namedArguments = this.VisitList(node.NamedArguments); TypeSymbol? type = this.VisitType(node.Type); return node.Update(node.Constructor, constructorArguments, node.ConstructorArgumentNamesOpt, node.ConstructorArgumentsToParamsOpt, node.ConstructorExpanded, namedArguments, node.ResultKind, type); } @@ -12407,7 +12407,7 @@ public NullabilityRewriter(ImmutableDictionary constructorArguments = this.VisitList(node.ConstructorArguments); - ImmutableArray namedArguments = this.VisitList(node.NamedArguments); + ImmutableArray namedArguments = this.VisitList(node.NamedArguments); BoundAttribute updatedNode; if (_updatedNullabilities.TryGetValue(node, out (NullabilityInfo Info, TypeSymbol? Type) infoAndType)) diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 7096e1231aece..0a5fa9815f2d9 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -96,7 +96,7 @@ private set internal BoundExpression MakeInvocationExpression( BinderFlags flags, SyntaxNode node, - BoundExpression? receiver, + BoundExpression receiver, string methodName, ImmutableArray args, BindingDiagnosticBag diagnostics, @@ -657,12 +657,12 @@ public BoundExpression MakeIsNotANumberTest(BoundExpression input) } } - public BoundExpression InstanceCall(BoundExpression? receiver, string name, BoundExpression arg) + public BoundExpression InstanceCall(BoundExpression receiver, string name, BoundExpression arg) { return MakeInvocationExpression(BinderFlags.None, this.Syntax, receiver, name, ImmutableArray.Create(arg), this.Diagnostics); } - public BoundExpression InstanceCall(BoundExpression? receiver, string name) + public BoundExpression InstanceCall(BoundExpression receiver, string name) { return MakeInvocationExpression(BinderFlags.None, this.Syntax, receiver, name, ImmutableArray.Empty, this.Diagnostics); } diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs index 6467b5d32c121..378deff3457b5 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs @@ -311,6 +311,8 @@ INamedTypeSymbol IMethodSymbol.AssociatedAnonymousDelegate bool IMethodSymbol.IsExtensionMethod => _underlying.IsExtensionMethod; + System.Reflection.MethodImplAttributes IMethodSymbol.MethodImplementationFlags => _underlying.ImplementationAttributes; + bool IMethodSymbol.IsVararg => _underlying.IsVararg; bool IMethodSymbol.IsCheckedBuiltin => _underlying.IsCheckedBuiltin; diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs index d615fae84c766..fd2c313e1b886 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs @@ -942,8 +942,6 @@ private TypeSymbol GetResolvedType() { if ((object)_resolved == null) { - Debug.Assert(_underlying.IsSafeToResolve()); - TryForceResolve(asValueType: _underlying.Type.IsValueType); } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs index 8b1f8f242b7a5..5c97040f8c82a 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs @@ -4,6 +4,8 @@ #nullable disable +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; @@ -4871,6 +4873,384 @@ public class B // [My(1 switch { 1 => 1, _ => string.Empty })] Diagnostic(ErrorCode.ERR_BadArgType, "1 switch { 1 => 1, _ => string.Empty }").WithArguments("1", "", "int").WithLocation(11, 9) ); + + var tree = compilation.SyntaxTrees[0]; + var model = compilation.GetSemanticModel(tree); + + var switchExpressions = tree.GetRoot().DescendantNodes().OfType().ToArray(); + + VerifyOperationTreeForNode(compilation, model, switchExpressions[0], @" +ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: '1 switch { ... 1, _ => 2 }') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + Arms(2): + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '1 => 1') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null, IsInvalid) (Syntax: '1') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '_ => 2') + Pattern: + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null, IsInvalid) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2, IsInvalid) (Syntax: '2') +"); + + VerifyOperationTreeForNode(compilation, model, switchExpressions[1], @" +ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: '1 switch { ... > new B() }') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + Arms(2): + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '1 => new A()') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null, IsInvalid) (Syntax: '1') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + Value: + IConversionOperation (TryCast: False, Unchecked) (OperatorMethod: System.Int32 A.op_Implicit(A a)) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'new A()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: True) (MethodSymbol: System.Int32 A.op_Implicit(A a)) + Operand: + IObjectCreationOperation (Constructor: A..ctor()) (OperationKind.ObjectCreation, Type: A, IsInvalid) (Syntax: 'new A()') + Arguments(0) + Initializer: + null + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '_ => new B()') + Pattern: + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null, IsInvalid) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperatorMethod: System.Int32 B.op_Implicit(B b)) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'new B()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: True) (MethodSymbol: System.Int32 B.op_Implicit(B b)) + Operand: + IObjectCreationOperation (Constructor: B..ctor()) (OperationKind.ObjectCreation, Type: B, IsInvalid) (Syntax: 'new B()') + Arguments(0) + Initializer: + null +"); + + VerifyOperationTreeForNode(compilation, model, switchExpressions[2], @" +ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: '1 switch { ... ing.Empty }') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + Arms(2): + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '1 => 1') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null, IsInvalid) (Syntax: '1') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsInvalid, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '_ => string.Empty') + Pattern: + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null, IsInvalid) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsInvalid, IsImplicit) (Syntax: 'string.Empty') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IFieldReferenceOperation: System.String System.String.Empty (Static) (OperationKind.FieldReference, Type: System.String, IsInvalid) (Syntax: 'string.Empty') + Instance Receiver: + null +"); + } + + [Fact] + public void TargetTypedSwitch_Attribute_NamedArgument() + { + var source = @" +using System; +class Program +{ + [My(Value = 1 switch { 1 => 1, _ => 2 })] + public static void M1() { } + + [My(Value = 1 switch { 1 => new A(), _ => new B() })] + public static void M2() { } + + [My(Value = 1 switch { 1 => 1, _ => string.Empty })] + public static void M3() { } +} +public class MyAttribute : Attribute +{ + public MyAttribute() { } + public int Value { get; set; } +} +public class A +{ + public static implicit operator int(A a) => 4; +} +public class B +{ + public static implicit operator int(B b) => 2; +} +"; + var compilation = CreateCompilation(source); + compilation.VerifyDiagnostics( + // (5,17): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // [My(Value = 1 switch { 1 => 1, _ => 2 })] + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "1 switch { 1 => 1, _ => 2 }").WithLocation(5, 17), + // (8,17): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // [My(Value = 1 switch { 1 => new A(), _ => new B() })] + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "1 switch { 1 => new A(), _ => new B() }").WithLocation(8, 17), + // (11,41): error CS0029: Cannot implicitly convert type 'string' to 'int' + // [My(Value = 1 switch { 1 => 1, _ => string.Empty })] + Diagnostic(ErrorCode.ERR_NoImplicitConv, "string.Empty").WithArguments("string", "int").WithLocation(11, 41) + ); + + var tree = compilation.SyntaxTrees[0]; + var model = compilation.GetSemanticModel(tree); + + var attributeArguments = tree.GetRoot().DescendantNodes().OfType().ToArray(); + + VerifyOperationTreeForNode(compilation, model, attributeArguments[0], @" +ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsInvalid) (Syntax: 'Value = 1 s ... 1, _ => 2 }') + Left: + IPropertyReferenceOperation: System.Int32 MyAttribute.Value { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'Value') + Instance Receiver: + null + Right: + ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: '1 switch { ... 1, _ => 2 }') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + Arms(2): + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '1 => 1') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null, IsInvalid) (Syntax: '1') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '_ => 2') + Pattern: + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null, IsInvalid) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2, IsInvalid) (Syntax: '2') +"); + + VerifyOperationTreeForNode(compilation, model, attributeArguments[1], @" +ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsInvalid) (Syntax: 'Value = 1 s ... > new B() }') + Left: + IPropertyReferenceOperation: System.Int32 MyAttribute.Value { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'Value') + Instance Receiver: + null + Right: + ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32, IsInvalid) (Syntax: '1 switch { ... > new B() }') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + Arms(2): + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '1 => new A()') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null, IsInvalid) (Syntax: '1') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + Value: + IConversionOperation (TryCast: False, Unchecked) (OperatorMethod: System.Int32 A.op_Implicit(A a)) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'new A()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: True) (MethodSymbol: System.Int32 A.op_Implicit(A a)) + Operand: + IObjectCreationOperation (Constructor: A..ctor()) (OperationKind.ObjectCreation, Type: A, IsInvalid) (Syntax: 'new A()') + Arguments(0) + Initializer: + null + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '_ => new B()') + Pattern: + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null, IsInvalid) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperatorMethod: System.Int32 B.op_Implicit(B b)) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'new B()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: True) (MethodSymbol: System.Int32 B.op_Implicit(B b)) + Operand: + IObjectCreationOperation (Constructor: B..ctor()) (OperationKind.ObjectCreation, Type: B, IsInvalid) (Syntax: 'new B()') + Arguments(0) + Initializer: + null +"); + + VerifyOperationTreeForNode(compilation, model, attributeArguments[2], @" +ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsInvalid) (Syntax: 'Value = 1 s ... ing.Empty }') + Left: + IPropertyReferenceOperation: System.Int32 MyAttribute.Value { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'Value') + Instance Receiver: + null + Right: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: '1 switch { ... ing.Empty }') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: ?, IsInvalid) (Syntax: '1 switch { ... ing.Empty }') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Arms(2): + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null) (Syntax: '1 => 1') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '1') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null, IsInvalid) (Syntax: '_ => string.Empty') + Pattern: + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsInvalid, IsImplicit) (Syntax: 'string.Empty') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IFieldReferenceOperation: System.String System.String.Empty (Static) (OperationKind.FieldReference, Type: System.String, IsInvalid) (Syntax: 'string.Empty') + Instance Receiver: + null +"); + } + + [Fact] + public void TargetTypedSwitch_Attribute_MissingNamedArgument() + { + var source = @" +using System; +class Program +{ + [My(Value = 1 switch { 1 => 1, _ => 2 })] + public static void M1() { } + + [My(Value = 1 switch { 1 => new A(), _ => new B() })] + public static void M2() { } + + [My(Value = 1 switch { 1 => 1, _ => string.Empty })] + public static void M3() { } +} +public class MyAttribute : Attribute +{ + public MyAttribute() { } +} +public class A +{ + public static implicit operator int(A a) => 4; +} +public class B +{ + public static implicit operator int(B b) => 2; +} +"; + var compilation = CreateCompilation(source); + compilation.VerifyDiagnostics( + // (5,9): error CS0246: The type or namespace name 'Value' could not be found (are you missing a using directive or an assembly reference?) + // [My(Value = 1 switch { 1 => 1, _ => 2 })] + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Value").WithArguments("Value").WithLocation(5, 9), + // (8,9): error CS0246: The type or namespace name 'Value' could not be found (are you missing a using directive or an assembly reference?) + // [My(Value = 1 switch { 1 => new A(), _ => new B() })] + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Value").WithArguments("Value").WithLocation(8, 9), + // (11,9): error CS0246: The type or namespace name 'Value' could not be found (are you missing a using directive or an assembly reference?) + // [My(Value = 1 switch { 1 => 1, _ => string.Empty })] + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Value").WithArguments("Value").WithLocation(11, 9) + ); + + var tree = compilation.SyntaxTrees[0]; + var model = compilation.GetSemanticModel(tree); + + var attributeArguments = tree.GetRoot().DescendantNodes().OfType().ToArray(); + + VerifyOperationTreeForNode(compilation, model, attributeArguments[0], @" +ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: ?, IsInvalid) (Syntax: 'Value = 1 s ... 1, _ => 2 }') + Left: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'Value') + Children(0) + Right: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsImplicit) (Syntax: '1 switch { ... 1, _ => 2 }') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: System.Int32) (Syntax: '1 switch { ... 1, _ => 2 }') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Arms(2): + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null) (Syntax: '1 => 1') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '1') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null) (Syntax: '_ => 2') + Pattern: + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') +"); + + VerifyOperationTreeForNode(compilation, model, attributeArguments[1], @" +ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: ?, IsInvalid) (Syntax: 'Value = 1 s ... > new B() }') + Left: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'Value') + Children(0) + Right: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsImplicit) (Syntax: '1 switch { ... > new B() }') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: ?) (Syntax: '1 switch { ... > new B() }') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Arms(2): + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null) (Syntax: '1 => new A()') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '1') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsImplicit) (Syntax: 'new A()') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IObjectCreationOperation (Constructor: A..ctor()) (OperationKind.ObjectCreation, Type: A) (Syntax: 'new A()') + Arguments(0) + Initializer: + null + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null) (Syntax: '_ => new B()') + Pattern: + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsImplicit) (Syntax: 'new B()') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IObjectCreationOperation (Constructor: B..ctor()) (OperationKind.ObjectCreation, Type: B) (Syntax: 'new B()') + Arguments(0) + Initializer: + null +"); + + VerifyOperationTreeForNode(compilation, model, attributeArguments[2], @" +ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: ?, IsInvalid) (Syntax: 'Value = 1 s ... ing.Empty }') + Left: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'Value') + Children(0) + Right: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsImplicit) (Syntax: '1 switch { ... ing.Empty }') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ISwitchExpressionOperation (2 arms) (OperationKind.SwitchExpression, Type: ?) (Syntax: '1 switch { ... ing.Empty }') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Arms(2): + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null) (Syntax: '1 => 1') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '1') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsImplicit) (Syntax: '1') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + ISwitchExpressionArmOperation (0 locals) (OperationKind.SwitchExpressionArm, Type: null) (Syntax: '_ => string.Empty') + Pattern: + IDiscardPatternOperation (OperationKind.DiscardPattern, Type: null) (Syntax: '_') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, IsImplicit) (Syntax: 'string.Empty') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + IFieldReferenceOperation: System.String System.String.Empty (Static) (OperationKind.FieldReference, Type: System.String) (Syntax: 'string.Empty') + Instance Receiver: + null +"); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit/Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj b/src/Compilers/CSharp/Test/Emit/Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj index 85d2492702f27..263273fa60cbc 100644 --- a/src/Compilers/CSharp/Test/Emit/Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj +++ b/src/Compilers/CSharp/Test/Emit/Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj @@ -30,5 +30,5 @@ - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IConditionalAccessExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IConditionalAccessExpression.cs index 957c6f4012e1d..dcf4952a800bb 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IConditionalAccessExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IConditionalAccessExpression.cs @@ -1563,5 +1563,189 @@ void M(C c) Statements (0) ", DiagnosticDescription.None); } + + [Fact] + public void InvalidConditionalAccess_01() + { + var code = @" +class C +{ + void M() + /**/{ + _ = 123?[1, 2]; + }/**/ +} +"; + + var expectedDiagnostics = new[] + { + // file.cs(6,16): error CS0023: Operator '?' cannot be applied to operand of type 'int' + // _ = 123?[1, 2]; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "int").WithLocation(6, 16) + }; + + VerifyFlowGraphAndDiagnosticsForTest(code, @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + CaptureIds: [0] + .locals {R2} + { + CaptureIds: [1] [2] [3] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '1') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '2') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '123') + Value: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid, IsImplicit) (Syntax: '123') + Children(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 123, IsInvalid) (Syntax: '123') + Jump if True (Regular) to Block[B3] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsInvalid, IsImplicit) (Syntax: '123') + Operand: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: ?, IsInvalid, IsImplicit) (Syntax: '123') + Leaving: {R2} + Next (Regular) Block[B2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '[1, 2]') + Value: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: '[1, 2]') + Children(3): + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '1') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 2, IsImplicit) (Syntax: '2') + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: ?, IsInvalid, IsImplicit) (Syntax: '123') + Next (Regular) Block[B4] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '123') + Value: + IDefaultValueOperation (OperationKind.DefaultValue, Type: ?, Constant: null, IsInvalid, IsImplicit) (Syntax: '123') + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: '_ = 123?[1, 2];') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: ?, IsInvalid) (Syntax: '_ = 123?[1, 2]') + Left: + IDiscardOperation (Symbol: ? _) (OperationKind.Discard, Type: ?) (Syntax: '_') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: ?, IsInvalid, IsImplicit) (Syntax: '123?[1, 2]') + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +", expectedDiagnostics); + } + + [Fact] + public void InvalidConditionalAccess_02() + { + var code = @" +class C +{ + void M() + /**/{ + _ = 123?[1, 2].ToString(); + }/**/ +} +"; + + var expectedDiagnostics = new[] + { + // file.cs(6,16): error CS0023: Operator '?' cannot be applied to operand of type 'int' + // _ = 123?[1, 2]; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "int").WithLocation(6, 16) + }; + + VerifyFlowGraphAndDiagnosticsForTest(code, @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + CaptureIds: [0] + .locals {R2} + { + CaptureIds: [1] [2] [3] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '1') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '2') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '123') + Value: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid, IsImplicit) (Syntax: '123') + Children(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 123, IsInvalid) (Syntax: '123') + Jump if True (Regular) to Block[B3] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsInvalid, IsImplicit) (Syntax: '123') + Operand: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: ?, IsInvalid, IsImplicit) (Syntax: '123') + Leaving: {R2} + Next (Regular) Block[B2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '[1, 2].ToString()') + Value: + IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: '[1, 2].ToString()') + Children(1): + IOperation: (OperationKind.None, Type: null, IsInvalid) (Syntax: '[1, 2].ToString') + Children(1): + IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: '[1, 2]') + Children(3): + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: '1') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 2, IsImplicit) (Syntax: '2') + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: ?, IsInvalid, IsImplicit) (Syntax: '123') + Next (Regular) Block[B4] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: '123') + Value: + IDefaultValueOperation (OperationKind.DefaultValue, Type: ?, Constant: null, IsInvalid, IsImplicit) (Syntax: '123') + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: '_ = 123?[1, ... ToString();') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: ?, IsInvalid) (Syntax: '_ = 123?[1, ... .ToString()') + Left: + IDiscardOperation (Symbol: ? _) (OperationKind.Discard, Type: ?) (Syntax: '_') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: ?, IsInvalid, IsImplicit) (Syntax: '123?[1, 2].ToString()') + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +", expectedDiagnostics); + } } } diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs index d778c6e6dcb36..fc94252b95daf 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IObjectCreationExpression.cs @@ -15183,5 +15183,135 @@ static void F(A a) Predecessors: [B3] Statements (0)"); } + + [Fact] + public void IndexedPropertyWithDefaultArgumentInVB() + { + var source1 = +@"Imports System +Imports System.Collections.Generic +Imports System.Runtime.InteropServices + + + + + +Public Interface IA + Property P1(Optional index As Integer = 1) As Object + ReadOnly Property P2(Optional index As Integer = 2) As IA +End Interface +Public Class A + Implements IA + Property P1(Optional index As Integer = 1) As Object Implements IA.P1 + Get + Return Nothing + End Get + Set(value As Object) + End Set + End Property + ReadOnly Property P2(Optional index As Integer = 2) As IA Implements IA.P2 + Get + Return New A() + End Get + End Property +End Class"; + var reference1 = BasicCompilationUtils.CompileToMetadata(source1, verify: Verification.Passes); + var source2 = +@"class B +{ + static void M(IA a) + /**/{ + a = new IA() { P2 = { P1 = 5 } }; + }/**/ +}"; + + VerifyFlowGraphAndDiagnosticsForTest(source2, expectedDiagnostics: DiagnosticDescription.None, references: new[] { reference1 }, + expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'a') + Value: + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: IA) (Syntax: 'a') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'new IA() { ... P1 = 5 } }') + Value: + IObjectCreationOperation (Constructor: A..ctor()) (OperationKind.ObjectCreation, Type: IA) (Syntax: 'new IA() { ... P1 = 5 } }') + Arguments(0) + Initializer: + null + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'P2') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2, IsImplicit) (Syntax: 'P2') + Next (Regular) Block[B3] + Entering: {R3} + .locals {R3} + { + CaptureIds: [3] + Block[B3] - Block + Predecessors: [B2] + Statements (2) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'P1') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: 'P1') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Object) (Syntax: 'P1 = 5') + Left: + IPropertyReferenceOperation: System.Object IA.P1[[System.Int32 index = 1]] { get; set; } (OperationKind.PropertyReference, Type: System.Object) (Syntax: 'P1') + Instance Receiver: + IPropertyReferenceOperation: IA IA.P2[[System.Int32 index = 2]] { get; } (OperationKind.PropertyReference, Type: IA) (Syntax: 'P2') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: IA, IsImplicit) (Syntax: 'new IA() { ... P1 = 5 } }') + Arguments(1): + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: index) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'P2') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 2, IsImplicit) (Syntax: 'P2') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Arguments(1): + IArgumentOperation (ArgumentKind.DefaultValue, Matching Parameter: index) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'P1') + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 1, IsImplicit) (Syntax: 'P1') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Right: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: '5') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 5) (Syntax: '5') + Next (Regular) Block[B4] + Leaving: {R3} {R2} + } + } + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'a = new IA( ... P1 = 5 } };') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: IA) (Syntax: 'a = new IA( ... P1 = 5 } }') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: IA, IsImplicit) (Syntax: 'a') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: IA, IsImplicit) (Syntax: 'new IA() { ... P1 = 5 } }') + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"); + } } } diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs index 9875cf6aa07a0..77d4298dd2c41 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUsingStatement.cs @@ -8487,5 +8487,486 @@ void M() Statements (0) "); } + + [Fact] + public void UsingDeclaration_InsideSwitchCaseEmbeddedStatements() + { + var source = @" +using System; +class C1 : IDisposable +{ + public void Dispose() { } +} +class C2 +{ + public static void M(int x) + /**/{ + switch (x) + { + case 5: + using C1 o1 = new C1(); + break; + } + }/**/ +}"; + + var expectedDiagnostics = new DiagnosticDescription[] { + // file.cs(14,17): error CS8647: A using variable cannot be used directly within a switch section (consider using braces). + // using C1 o1 = new C1(); + Diagnostic(ErrorCode.ERR_UsingVarInSwitchCase, "using C1 o1 = new C1();").WithLocation(14, 17) + }; + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'x') + Value: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'x') + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + Locals: [C1 o1] + Block[B2] - Block + Predecessors: [B1] + Statements (0) + Jump if False (Regular) to Block[B8] + IBinaryOperation (BinaryOperatorKind.Equals) (OperationKind.Binary, Type: System.Boolean, IsImplicit) (Syntax: '5') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'x') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 5) (Syntax: '5') + Leaving: {R2} {R1} + Next (Regular) Block[B3] + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Left: + ILocalReferenceOperation: o1 (IsDeclaration: True) (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Right: + IObjectCreationOperation (Constructor: C1..ctor()) (OperationKind.ObjectCreation, Type: C1, IsInvalid) (Syntax: 'new C1()') + Arguments(0) + Initializer: + null + Next (Regular) Block[B4] + Entering: {R3} {R4} + .try {R3, R4} + { + Block[B4] - Block + Predecessors: [B3] + Statements (0) + Next (Regular) Block[B8] + Finalizing: {R5} + Leaving: {R4} {R3} {R2} {R1} + } + .finally {R5} + { + Block[B5] - Block + Predecessors (0) + Statements (0) + Jump if True (Regular) to Block[B7] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Operand: + ILocalReferenceOperation: o1 (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Next (Regular) Block[B6] + Block[B6] - Block + Predecessors: [B5] + Statements (1) + IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Instance Receiver: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: o1 (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Arguments(0) + Next (Regular) Block[B7] + Block[B7] - Block + Predecessors: [B5] [B6] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } + } +} +Block[B8] - Exit + Predecessors: [B2] [B4] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); + } + + [Fact] + public void UsingDeclaration_InsideIfEmbeddedStatement() + { + var source = @" +using System; +class C1 : IDisposable +{ + public void Dispose() { } +} +class C2 +{ + public static void M(bool b) + /**/{ + if (b) + using C1 o1 = new C1(); + }/**/ +}"; + + var expectedDiagnostics = new DiagnosticDescription[] { + // file.cs(12,13): error CS1023: Embedded statement cannot be a declaration or labeled statement + // using C1 o1 = new C1(); + Diagnostic(ErrorCode.ERR_BadEmbeddedStmt, "using C1 o1 = new C1();").WithLocation(12, 13) + }; + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B7] + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + Next (Regular) Block[B2] + Entering: {R1} +.locals {R1} +{ + Locals: [C1 o1] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Left: + ILocalReferenceOperation: o1 (IsDeclaration: True) (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Right: + IObjectCreationOperation (Constructor: C1..ctor()) (OperationKind.ObjectCreation, Type: C1, IsInvalid) (Syntax: 'new C1()') + Arguments(0) + Initializer: + null + Next (Regular) Block[B3] + Entering: {R2} {R3} + .try {R2, R3} + { + Block[B3] - Block + Predecessors: [B2] + Statements (0) + Next (Regular) Block[B7] + Finalizing: {R4} + Leaving: {R3} {R2} {R1} + } + .finally {R4} + { + Block[B4] - Block + Predecessors (0) + Statements (0) + Jump if True (Regular) to Block[B6] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Operand: + ILocalReferenceOperation: o1 (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B4] + Statements (1) + IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Instance Receiver: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: o1 (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Arguments(0) + Next (Regular) Block[B6] + Block[B6] - Block + Predecessors: [B4] [B5] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } +} +Block[B7] - Exit + Predecessors: [B1] [B3] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); + } + + [Fact] + public void UsingDeclaration_InsideForEmbeddedStatement() + { + var source = @" +using System; +using System.Collections; +class C1 : IDisposable +{ + public void Dispose() { } +} +class C2 +{ + public static void M() + /**/{ + for (;;) + using C1 o1 = new C1(); + }/**/ +}"; + + var expectedDiagnostics = new DiagnosticDescription[] { + // file.cs(13,13): error CS1023: Embedded statement cannot be a declaration or labeled statement + // using C1 o1 = new C1(); + Diagnostic(ErrorCode.ERR_BadEmbeddedStmt, "using C1 o1 = new C1();").WithLocation(13, 13) + }; + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [C1 o1] + Block[B1] - Block + Predecessors: [B0] [B6] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Left: + ILocalReferenceOperation: o1 (IsDeclaration: True) (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Right: + IObjectCreationOperation (Constructor: C1..ctor()) (OperationKind.ObjectCreation, Type: C1, IsInvalid) (Syntax: 'new C1()') + Arguments(0) + Initializer: + null + Next (Regular) Block[B2] + Entering: {R2} {R3} + .try {R2, R3} + { + Block[B2] - Block + Predecessors: [B1] + Statements (0) + Next (Regular) Block[B6] + Finalizing: {R4} + Leaving: {R3} {R2} {R1} + } + .finally {R4} + { + Block[B3] - Block + Predecessors (0) + Statements (0) + Jump if True (Regular) to Block[B5] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Operand: + ILocalReferenceOperation: o1 (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Instance Receiver: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: o1 (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Arguments(0) + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B3] [B4] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } +} +Block[B6] - Block + Predecessors: [B2] + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +Block[B7] - Exit [UnReachable] + Predecessors (0) + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); + } + + [Fact] + public void UsingDeclaration_InsideForEachEmbeddedStatement() + { + var source = @" +using System; +using System.Collections; +class C1 : IDisposable +{ + public void Dispose() { } +} +class C2 +{ + public static void M(IEnumerable e) + /**/{ + foreach (var o in e) + using C1 o1 = new C1(); + }/**/ +}"; + + var expectedDiagnostics = new DiagnosticDescription[] { + // file.cs(13,13): error CS1023: Embedded statement cannot be a declaration or labeled statement + // using C1 o1 = new C1(); + Diagnostic(ErrorCode.ERR_BadEmbeddedStmt, "using C1 o1 = new C1();").WithLocation(13, 13) + }; + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'e') + Value: + IInvocationOperation (virtual System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()) (OperationKind.Invocation, Type: System.Collections.IEnumerator, IsImplicit) (Syntax: 'e') + Instance Receiver: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Collections.IEnumerable, IsImplicit) (Syntax: 'e') + Conversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + Operand: + IParameterReferenceOperation: e (OperationKind.ParameterReference, Type: System.Collections.IEnumerable) (Syntax: 'e') + Arguments(0) + Next (Regular) Block[B2] + Entering: {R2} {R3} + .try {R2, R3} + { + Block[B2] - Block + Predecessors: [B1] [B5] + Statements (0) + Jump if False (Regular) to Block[B12] + IInvocationOperation (virtual System.Boolean System.Collections.IEnumerator.MoveNext()) (OperationKind.Invocation, Type: System.Boolean, IsImplicit) (Syntax: 'e') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Collections.IEnumerator, IsImplicit) (Syntax: 'e') + Arguments(0) + Finalizing: {R9} + Leaving: {R3} {R2} {R1} + Next (Regular) Block[B3] + Entering: {R4} + .locals {R4} + { + Locals: [System.Object o] + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: null, IsImplicit) (Syntax: 'var') + Left: + ILocalReferenceOperation: o (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Object, IsImplicit) (Syntax: 'var') + Right: + IPropertyReferenceOperation: System.Object System.Collections.IEnumerator.Current { get; } (OperationKind.PropertyReference, Type: System.Object, IsImplicit) (Syntax: 'var') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Collections.IEnumerator, IsImplicit) (Syntax: 'e') + Next (Regular) Block[B4] + Entering: {R5} + .locals {R5} + { + Locals: [C1 o1] + Block[B4] - Block + Predecessors: [B3] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Left: + ILocalReferenceOperation: o1 (IsDeclaration: True) (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Right: + IObjectCreationOperation (Constructor: C1..ctor()) (OperationKind.ObjectCreation, Type: C1, IsInvalid) (Syntax: 'new C1()') + Arguments(0) + Initializer: + null + Next (Regular) Block[B5] + Entering: {R6} {R7} + .try {R6, R7} + { + Block[B5] - Block + Predecessors: [B4] + Statements (0) + Next (Regular) Block[B2] + Finalizing: {R8} + Leaving: {R7} {R6} {R5} {R4} + } + .finally {R8} + { + Block[B6] - Block + Predecessors (0) + Statements (0) + Jump if True (Regular) to Block[B8] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Operand: + ILocalReferenceOperation: o1 (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Next (Regular) Block[B7] + Block[B7] - Block + Predecessors: [B6] + Statements (1) + IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Instance Receiver: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: o1 (OperationKind.LocalReference, Type: C1, IsInvalid, IsImplicit) (Syntax: 'o1 = new C1()') + Arguments(0) + Next (Regular) Block[B8] + Block[B8] - Block + Predecessors: [B6] [B7] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } + } + } + } + .finally {R9} + { + CaptureIds: [1] + Block[B9] - Block + Predecessors (0) + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'e') + Value: + IConversionOperation (TryCast: True, Unchecked) (OperationKind.Conversion, Type: System.IDisposable, IsImplicit) (Syntax: 'e') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ExplicitReference) + Operand: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Collections.IEnumerator, IsImplicit) (Syntax: 'e') + Jump if True (Regular) to Block[B11] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'e') + Operand: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.IDisposable, IsImplicit) (Syntax: 'e') + Next (Regular) Block[B10] + Block[B10] - Block + Predecessors: [B9] + Statements (1) + IInvocationOperation (virtual void System.IDisposable.Dispose()) (OperationKind.Invocation, Type: System.Void, IsImplicit) (Syntax: 'e') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.IDisposable, IsImplicit) (Syntax: 'e') + Arguments(0) + Next (Regular) Block[B11] + Block[B11] - Block + Predecessors: [B9] [B10] + Statements (0) + Next (StructuredExceptionHandling) Block[null] + } +} +Block[B12] - Exit + Predecessors: [B2] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); + } } } diff --git a/src/Compilers/CSharp/Test/IOperation/Microsoft.CodeAnalysis.CSharp.IOperation.UnitTests.csproj b/src/Compilers/CSharp/Test/IOperation/Microsoft.CodeAnalysis.CSharp.IOperation.UnitTests.csproj index fa56463514b76..125343cfda957 100644 --- a/src/Compilers/CSharp/Test/IOperation/Microsoft.CodeAnalysis.CSharp.IOperation.UnitTests.csproj +++ b/src/Compilers/CSharp/Test/IOperation/Microsoft.CodeAnalysis.CSharp.IOperation.UnitTests.csproj @@ -21,5 +21,5 @@ - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Semantic/Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.csproj b/src/Compilers/CSharp/Test/Semantic/Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.csproj index e6be84fb87db4..0a1c989caf04b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.csproj +++ b/src/Compilers/CSharp/Test/Semantic/Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.csproj @@ -22,5 +22,5 @@ - + \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index bd15620ed9f2e..e7dcc8b1a5108 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -13437,6 +13437,12 @@ class B : IA } "; var compilation = CreateCompilation(new[] { source }, options: WithNullableEnable()); + + var tree = compilation.SyntaxTrees[0]; + var model = compilation.GetSemanticModel(tree); + var returnStatement = tree.GetRoot().DescendantNodes().OfType().Skip(1).Single(); + AssertEx.Equal("S?[]", model.GetTypeInfo(returnStatement.Expression).Type.ToTestDisplayString()); + compilation.VerifyDiagnostics( // (17,6): warning CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. // S?[] IA.M2() where S : class diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs index 8f462e286ab52..188a65c7fe99f 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/CompilationAPITests.cs @@ -2192,7 +2192,7 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere } } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoUsedAssembliesValidation), typeof(NoIOperationValidation), Reason = "IOperation skip: Compilation changes over time, adds new errors")] public void MetadataConsistencyWhileEvolvingCompilation() { var md1 = AssemblyMetadata.CreateFromImage(CreateCompilation("public class C { }").EmitToArray()); diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/ReferenceManagerTests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/ReferenceManagerTests.cs index ff6b62174c9b0..18d41de5dea5c 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/ReferenceManagerTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/ReferenceManagerTests.cs @@ -2525,7 +2525,7 @@ public class P "mscorlib: global,Y,Y,Z"); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution1() { // c - a -> b @@ -2628,7 +2628,7 @@ public class C : A "B, Version=3.0.0.0: Y,X"); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_WeakIdentities1() { // c - a -> "b,v1,PKT=null" @@ -2664,7 +2664,7 @@ public void MissingAssemblyResolution_WeakIdentities1() "B, Version=1.0.0.0: "); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_WeakIdentities2() { // c - a -> "b,v1,PKT=null" @@ -2717,7 +2717,7 @@ public void MissingAssemblyResolution_None() resolver.VerifyResolutionAttempts(); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_ActualMissing() { // c - a -> d @@ -2740,7 +2740,7 @@ public void MissingAssemblyResolution_ActualMissing() /// /// Ignore assemblies returned by the resolver that don't match the reference identity. /// - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_MissingDueToResolutionMismatch() { // c - a -> b @@ -2765,7 +2765,7 @@ public void MissingAssemblyResolution_MissingDueToResolutionMismatch() "A -> B, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_Multiple() { // c - a -> d @@ -2790,7 +2790,7 @@ public void MissingAssemblyResolution_Multiple() "B -> D, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_Modules() { // c - a - d @@ -2994,7 +2994,7 @@ public void MissingAssemblyResolution_BindingToExplicitReference_BetterVersion() "E, Version=1.0.0.0"); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_BindingToImplicitReference1() { // c - a -> d -> "b,v2" @@ -3038,7 +3038,7 @@ public void MissingAssemblyResolution_BindingToImplicitReference1() "B, Version=1.0.0.0: "); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_BindingToImplicitReference2() { // c - a -> d -> "b,v2" @@ -3104,7 +3104,7 @@ public void MissingAssemblyResolution_BindingToImplicitReference2() "A -> B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ce65828c82a341f2"); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_BindingToImplicitReference3() { // c - a -> d -> "b,v2" @@ -3170,7 +3170,7 @@ public void MissingAssemblyResolution_BindingToImplicitReference3() "A -> B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ce65828c82a341f2"); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_Supersession_FxUnification() { var options = TestOptions.ReleaseDll.WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default); @@ -3213,7 +3213,7 @@ public void MissingAssemblyResolution_Supersession_FxUnification() "System, Version=2.0.0.0: "); } - [ConditionalFact(typeof(NoUsedAssembliesValidation))] + [ConditionalFact(typeof(NoIOperationValidation), typeof(NoUsedAssembliesValidation), Reason = "IOperation adds extra assemblies")] public void MissingAssemblyResolution_Supersession_StrongNames() { var options = TestOptions.ReleaseDll.WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default); diff --git a/src/Compilers/CSharp/Test/Symbol/Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj b/src/Compilers/CSharp/Test/Symbol/Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj index 222edb6d31c18..b457b865a4d00 100644 --- a/src/Compilers/CSharp/Test/Symbol/Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj +++ b/src/Compilers/CSharp/Test/Symbol/Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj @@ -20,5 +20,5 @@ - + diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MethodImplementationFlagsTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MethodImplementationFlagsTests.cs new file mode 100644 index 0000000000000..2fde51c4104a6 --- /dev/null +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MethodImplementationFlagsTests.cs @@ -0,0 +1,189 @@ +// 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.Reflection; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols +{ + public class MethodImplementationFlagsTests : CSharpTestBase + { + [Fact] + public void TestInliningFlags() + { + var src = @" +using System.Runtime.CompilerServices; + +public class C +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void M_Aggressive() + { + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void M_NoInlining() + { + } +} +"; + + Action validator = module => + { + var c = module.GlobalNamespace.GetMember("C"); + var aggressiveInliningMethod = c.GetMember("M_Aggressive").GetPublicSymbol(); + Assert.Equal(MethodImplAttributes.AggressiveInlining, aggressiveInliningMethod.MethodImplementationFlags); + + var noInliningMethod = c.GetMember("M_NoInlining").GetPublicSymbol(); + Assert.Equal(MethodImplAttributes.NoInlining, noInliningMethod.MethodImplementationFlags); + }; + + CompileAndVerify(src, sourceSymbolValidator: validator, symbolValidator: validator); + } + + + [Fact] + public void TestOptimizationFlags() + { + var src = @" +using System.Runtime.CompilerServices; + +public class C +{ + [MethodImpl((MethodImplOptions)512)] // Aggressive optimization + public void M_Aggressive() + { + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + public void M_NoOptimization() + { + } +} +"; + Action validator = module => + { + var c = module.GlobalNamespace.GetMember("C"); + var aggressiveOptimizationMethod = c.GetMember("M_Aggressive").GetPublicSymbol(); +#if !NET472 // MethodImplAttributes.AggressiveOptimization was introduced in .NET Core 3 + Assert.Equal(MethodImplAttributes.AggressiveOptimization, aggressiveOptimizationMethod.MethodImplementationFlags); +#else + Assert.Equal((MethodImplAttributes)512, aggressiveOptimizationMethod.MethodImplementationFlags); +#endif + + var noOptimizationMethod = c.GetMember("M_NoOptimization").GetPublicSymbol(); + Assert.Equal(MethodImplAttributes.NoOptimization, noOptimizationMethod.MethodImplementationFlags); + }; + + CompileAndVerify(src, sourceSymbolValidator: validator, symbolValidator: validator); + } + + [Fact] + public void TestMixingOptimizationWithInliningFlags() + { + var src = @" +using System.Runtime.CompilerServices; + +public class C +{ + [MethodImpl((MethodImplOptions)512 | MethodImplOptions.NoInlining)] // aggressive optimization and no inlining + public void M_AggressiveOpt_NoInlining() + { + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public void M_NoOpt_NoInlining() + { + } + + [MethodImpl((MethodImplOptions)512 | MethodImplOptions.AggressiveInlining)] // aggressive optimization and aggressive inlining + public void M_AggressiveOpt_AggressiveInlining() + { + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.AggressiveInlining)] + public void M_NoOpt_AggressiveInlining() + { + } +} +"; + + Action validator = module => + { + var c = module.GlobalNamespace.GetMember("C"); + var aggressiveOptNoInliningMethod = c.GetMember("M_AggressiveOpt_NoInlining").GetPublicSymbol(); +#if !NET472 // MethodImplAttributes.AggressiveOptimization was introduced in .NET Core 3 + Assert.Equal(MethodImplAttributes.AggressiveOptimization | MethodImplAttributes.NoInlining, aggressiveOptNoInliningMethod.MethodImplementationFlags); +#else + Assert.Equal((MethodImplAttributes)512 | MethodImplAttributes.NoInlining, aggressiveOptNoInliningMethod.MethodImplementationFlags); +#endif + + var noOptNoInliningMethod = c.GetMember("M_NoOpt_NoInlining").GetPublicSymbol(); + Assert.Equal(MethodImplAttributes.NoOptimization | MethodImplAttributes.NoInlining, noOptNoInliningMethod.MethodImplementationFlags); + + var aggressiveOptAggressiveInliningMethod = c.GetMember("M_AggressiveOpt_AggressiveInlining").GetPublicSymbol(); +#if !NET472 + Assert.Equal(MethodImplAttributes.AggressiveOptimization | MethodImplAttributes.AggressiveInlining, aggressiveOptAggressiveInliningMethod.MethodImplementationFlags); +#else + Assert.Equal((MethodImplAttributes)512 | MethodImplAttributes.AggressiveInlining, aggressiveOptAggressiveInliningMethod.MethodImplementationFlags); +#endif + + var noOptAggressiveInliningMethod = c.GetMember("M_NoOpt_AggressiveInlining").GetPublicSymbol(); + Assert.Equal(MethodImplAttributes.NoOptimization | MethodImplAttributes.AggressiveInlining, noOptAggressiveInliningMethod.MethodImplementationFlags); + }; + + CompileAndVerify(src, sourceSymbolValidator: validator, symbolValidator: validator); + } + + [Fact] + public void TestPreserveSigAndRuntimeFlags() + { + var src = @" +using System.Runtime.CompilerServices; + +public class C +{ + [MethodImpl(MethodImplOptions.PreserveSig, MethodCodeType = MethodCodeType.Runtime)] + public void M() + { + } +} +"; + Action validator = module => + { + var c = module.GlobalNamespace.GetMember("C"); + var method = c.GetMember("M").GetPublicSymbol(); + Assert.Equal(MethodImplAttributes.PreserveSig | MethodImplAttributes.Runtime, method.MethodImplementationFlags); + }; + + CompileAndVerify(src, sourceSymbolValidator: validator, symbolValidator: validator, verify: Verification.Skipped); + } + + [Fact] + public void TestNativeFlag() + { + var src = @" +using System.Runtime.CompilerServices; + +public class C +{ + [MethodImpl(MethodCodeType = MethodCodeType.Native)] + public extern void M(); +} +"; + Action validator = module => + { + var c = module.GlobalNamespace.GetMember("C"); + var method = c.GetMember("M").GetPublicSymbol(); + Assert.Equal(MethodImplAttributes.Native, method.MethodImplementationFlags); + }; + + CompileAndVerify(src, sourceSymbolValidator: validator, symbolValidator: validator, verify: Verification.Skipped); + } + } +} diff --git a/src/Compilers/CSharp/Test/WinRT/Microsoft.CodeAnalysis.CSharp.WinRT.UnitTests.csproj b/src/Compilers/CSharp/Test/WinRT/Microsoft.CodeAnalysis.CSharp.WinRT.UnitTests.csproj index d356fd48dad1f..72a86bd676d9a 100644 --- a/src/Compilers/CSharp/Test/WinRT/Microsoft.CodeAnalysis.CSharp.WinRT.UnitTests.csproj +++ b/src/Compilers/CSharp/Test/WinRT/Microsoft.CodeAnalysis.CSharp.WinRT.UnitTests.csproj @@ -18,5 +18,5 @@ - + diff --git a/src/Compilers/CSharp/csc/csc.csproj b/src/Compilers/CSharp/csc/csc.csproj index 01e6741bb0961..cc6e6cabf8098 100644 --- a/src/Compilers/CSharp/csc/csc.csproj +++ b/src/Compilers/CSharp/csc/csc.csproj @@ -12,6 +12,7 @@ true false true + true diff --git a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs index cb2299046871d..025896ce62492 100644 --- a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs +++ b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs @@ -422,6 +422,22 @@ public static async Task AnyAsync(this ImmutableArray array, Func AnyAsync(this ImmutableArray array, Func> predicateAsync, TArg arg) + { + int n = array.Length; + for (int i = 0; i < n; i++) + { + var a = array[i]; + + if (await predicateAsync(a, arg).ConfigureAwait(false)) + { + return true; + } + } + + return false; + } + public static async ValueTask FirstOrDefaultAsync(this ImmutableArray array, Func> predicateAsync) { int n = array.Length; diff --git a/src/Compilers/Core/Portable/Collections/StaticCast.cs b/src/Compilers/Core/Portable/Collections/StaticCast.cs index c051c2972ef7b..9a869e8796138 100644 --- a/src/Compilers/Core/Portable/Collections/StaticCast.cs +++ b/src/Compilers/Core/Portable/Collections/StaticCast.cs @@ -10,10 +10,7 @@ internal static class StaticCast { internal static ImmutableArray From(ImmutableArray from) where TDerived : class, T { - // Remove the pragma when we get a version with https://github.com/dotnet/runtime/issues/39799 fixed -#pragma warning disable CS8634 return ImmutableArray.CastUp(from); -#pragma warning restore CS8634 } } } diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs index 0fbc3ee8b7b11..8f4c31390db47 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs @@ -311,10 +311,16 @@ internal VariableSlotAllocator TryCreateVariableSlotAllocator(EmitBaseline basel symbolMap = MapToMetadataSymbolMatcher; } + var previousMethod = mappedMethod.PreviousMethod; + if (previousMethod is null) + { + previousMethod = (IMethodSymbolInternal)symbolMap.MapDefinitionOrNamespace(topLevelMethod); + } + return new EncVariableSlotAllocator( symbolMap, mappedMethod.SyntaxMap, - mappedMethod.PreviousMethod, + previousMethod, methodId, previousLocals, lambdaMap, diff --git a/src/Compilers/Core/Portable/Emit/SemanticEdit.cs b/src/Compilers/Core/Portable/Emit/SemanticEdit.cs index 6e94ee5e55467..caea2c3ce8e91 100644 --- a/src/Compilers/Core/Portable/Emit/SemanticEdit.cs +++ b/src/Compilers/Core/Portable/Emit/SemanticEdit.cs @@ -53,7 +53,7 @@ public struct SemanticEdit : IEquatable /// /// The type of edit. /// - /// The symbol from the earlier compilation, or null if the edit represents an addition. + /// The symbol from the earlier compilation, or null if the edit represents an addition or an update of the symbol from the previous compilation that exactly matches . /// /// /// The symbol from the later compilation, or null if the edit represents a deletion. @@ -74,7 +74,7 @@ public struct SemanticEdit : IEquatable /// public SemanticEdit(SemanticEditKind kind, ISymbol? oldSymbol, ISymbol? newSymbol, Func? syntaxMap = null, bool preserveLocalVariables = false) { - if (oldSymbol == null && kind != SemanticEditKind.Insert) + if (oldSymbol == null && kind is not (SemanticEditKind.Insert or SemanticEditKind.Update)) { throw new ArgumentNullException(nameof(oldSymbol)); } diff --git a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj index ca5d78b8ad38b..df2f704340471 100644 --- a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj @@ -11,7 +11,6 @@ true partial true - true true diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs index 314a3491cf714..62885c64f7492 100644 --- a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs @@ -3301,6 +3301,11 @@ void resetConditionalAccessTracker() static bool isConditionalAccessInstancePresentInChildren(IOperation operation) { + if (operation is InvalidOperation invalidOperation) + { + return checkInvalidChildren(invalidOperation); + } + // The conditional access should always be first leaf node in the subtree when performing a depth-first search. Visit the first child recursively // until we either reach the bottom, or find the conditional access. Operation currentOperation = (Operation)operation; @@ -3310,12 +3315,31 @@ static bool isConditionalAccessInstancePresentInChildren(IOperation operation) { return true; } + else if (enumerator.Current is InvalidOperation invalidChild) + { + return checkInvalidChildren(invalidChild); + } currentOperation = (Operation)enumerator.Current; } return false; } + + static bool checkInvalidChildren(InvalidOperation operation) + { + // Invalid operations can have children ordering that doesn't put the conditional access instance first. For these cases, + // use a recursive check + foreach (var child in operation.ChildOperations) + { + if (child is IConditionalAccessInstanceOperation || isConditionalAccessInstancePresentInChildren(child)) + { + return true; + } + } + + return false; + } } public override IOperation VisitConditionalAccessInstance(IConditionalAccessInstanceOperation operation, int? captureIdForResult) diff --git a/src/Compilers/Core/Portable/Operations/OperationNodes.cs b/src/Compilers/Core/Portable/Operations/OperationNodes.cs index 23a84af044677..dab2d1375d057 100644 --- a/src/Compilers/Core/Portable/Operations/OperationNodes.cs +++ b/src/Compilers/Core/Portable/Operations/OperationNodes.cs @@ -543,8 +543,9 @@ private BlockOperation(ImmutableArray statements, SemanticModel sema // Intentionally skipping SetParentOperation: this is used by CreateTemporaryBlock for the purposes of the // control flow factory, to temporarily create a new block for use in emulating the "block" a using variable // declaration introduces. These statements already have a parent node, and `SetParentOperation`'s verification - // would fail because that parent isn't this. - Debug.Assert(statements.All(s => s.Parent != this && s.Parent!.Kind == OperationKind.Block)); + // would fail because that parent isn't this. In error cases, the parent can also be a switch statement if + // the using declaration was used directly as an embedded statement in the case without a block. + Debug.Assert(statements.All(s => s.Parent != this && s.Parent!.Kind is OperationKind.Block or OperationKind.SwitchCase)); Operations = statements; Locals = ImmutableArray.Empty; } diff --git a/src/Compilers/Core/Portable/PEWriter/CompilationOptionNames.cs b/src/Compilers/Core/Portable/PEWriter/CompilationOptionNames.cs index fb4383b181cdf..5f12a18c7774b 100644 --- a/src/Compilers/Core/Portable/PEWriter/CompilationOptionNames.cs +++ b/src/Compilers/Core/Portable/PEWriter/CompilationOptionNames.cs @@ -28,7 +28,13 @@ internal static class CompilationOptionNames public const string Unsafe = "unsafe"; public const string Nullable = "nullable"; public const string Define = "define"; - public const string Strict = "strict"; public const string SourceFileCount = "source-file-count"; + public const string EmbedRuntime = "embed-runtime"; + public const string GlobalNamespaces = "global-namespaces"; + public const string RootNamespace = "root-namespace"; + public const string OptionStrict = "option-strict"; + public const string OptionInfer = "option-infer"; + public const string OptionExplicit = "option-explicit"; + public const string OptionCompareText = "option-compare-text"; } } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 72b91127ec216..220f5b680ddfd 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -15,6 +15,7 @@ Microsoft.CodeAnalysis.GeneratorPostInitializationContext.AddSource(string! hint Microsoft.CodeAnalysis.GeneratorPostInitializationContext.AddSource(string! hintName, string! source) -> void Microsoft.CodeAnalysis.GeneratorPostInitializationContext.CancellationToken.get -> System.Threading.CancellationToken Microsoft.CodeAnalysis.GeneratorPostInitializationContext.GeneratorPostInitializationContext() -> void +Microsoft.CodeAnalysis.IMethodSymbol.MethodImplementationFlags.get -> System.Reflection.MethodImplAttributes Microsoft.CodeAnalysis.ITypeSymbol.IsRecord.get -> bool Microsoft.CodeAnalysis.Operations.OperationWalker Microsoft.CodeAnalysis.Operations.OperationWalker.OperationWalker() -> void diff --git a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs index 211b5102b2ea8..74b62ca605838 100644 --- a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Reflection; using System.Reflection.Metadata; namespace Microsoft.CodeAnalysis @@ -263,6 +264,11 @@ public interface IMethodSymbol : ISymbol /// IMethodSymbol? PartialImplementationPart { get; } + /// + /// Returns the implementation flags for the given method symbol. + /// + MethodImplAttributes MethodImplementationFlags { get; } + /// /// Return true if this is a partial method definition without a body. If there /// is an implementing body, it can be retrieved with . diff --git a/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs b/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs index b6221f785c822..0c94c49ea6296 100644 --- a/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs +++ b/src/Compilers/Test/Core/Compilation/CompilationExtensions.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. #nullable disable +// Uncomment to enable the IOperation test hook on all test runs. Do not commit this uncommented. +//#define ROSLYN_TEST_IOPERATION using System; using System.Collections.Generic; @@ -30,7 +32,13 @@ namespace Microsoft.CodeAnalysis.Test.Utilities { public static class CompilationExtensions { - internal static bool EnableVerifyIOperation { get; } = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ROSLYN_TEST_IOPERATION")); + internal static bool EnableVerifyIOperation { get; } = +#if ROSLYN_TEST_IOPERATION + true; +#else + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ROSLYN_TEST_IOPERATION")); +#endif + internal static bool EnableVerifyUsedAssemblies { get; } = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ROSLYN_TEST_USEDASSEMBLIES")); internal static ImmutableArray EmitToArray( diff --git a/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs b/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs index 813df800ed910..71d1f4be45a2d 100644 --- a/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs +++ b/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs @@ -823,7 +823,8 @@ void assertCaptureReferences( ((isFirstOperandOfDynamicOrUserDefinedLogicalOperator(reference) || isIncrementedNullableForToLoopControlVariable(reference) || isConditionalAccessReceiver(reference) || - isCoalesceAssignmentTarget(reference)) && + isCoalesceAssignmentTarget(reference) || + isObjectInitializerInitializedObjectTarget(reference)) && block.EnclosingRegion.EnclosingRegion.CaptureIds.Contains(id)), $"Operation [{operationIndex}] in [{getBlockId(block)}] uses capture [{id.Value}] from another region. Should the regions be merged?", finalGraph); } @@ -875,6 +876,22 @@ bool isCoalesceAssignmentTarget(IFlowCaptureReferenceOperation reference) conditionalAccess.Left == referenceSyntax; } + bool isObjectInitializerInitializedObjectTarget(IFlowCaptureReferenceOperation reference) + { + if (reference.Language != LanguageNames.CSharp) + { + return false; + } + + CSharpSyntaxNode referenceSyntax = applyParenthesizedOrNullSuppressionIfAnyCS((CSharpSyntaxNode)reference.Syntax); + return referenceSyntax.Parent is CSharp.Syntax.AssignmentExpressionSyntax + { + RawKind: (int)CSharp.SyntaxKind.SimpleAssignmentExpression, + Parent: InitializerExpressionSyntax { Parent: CSharp.Syntax.ObjectCreationExpressionSyntax }, + Left: var left + } && left == referenceSyntax; + } + bool isFirstOperandOfDynamicOrUserDefinedLogicalOperator(IFlowCaptureReferenceOperation reference) { if (reference.Parent is IBinaryOperation binOp) diff --git a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb index caccff3f41701..b4d0b13fe941d 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb @@ -751,23 +751,32 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ' LanguageVersion should already be mapped to an effective version at this point Debug.Assert(LanguageVersion.MapSpecifiedToEffectiveVersion() = LanguageVersion) WriteValue(builder, CompilationOptionNames.LanguageVersion, LanguageVersion.ToDisplayString()) + WriteValue(builder, CompilationOptionNames.Checked, Options.CheckOverflow.ToString()) + WriteValue(builder, CompilationOptionNames.OptionStrict, Options.OptionStrict.ToString()) + WriteValue(builder, CompilationOptionNames.OptionInfer, Options.OptionInfer.ToString()) + WriteValue(builder, CompilationOptionNames.OptionCompareText, Options.OptionCompareText.ToString()) + WriteValue(builder, CompilationOptionNames.OptionExplicit, Options.OptionExplicit.ToString()) + WriteValue(builder, CompilationOptionNames.EmbedRuntime, Options.EmbedVbCoreRuntime.ToString()) - If Options.CheckOverflow Then - WriteValue(builder, CompilationOptionNames.Checked, Options.CheckOverflow.ToString()) + If Options.GlobalImports.Length > 0 Then + WriteValue(builder, CompilationOptionNames.GlobalNamespaces, String.Join(";", Options.GlobalImports.Select(Function(x) x.Name))) End If - If Options.OptionStrict <> OptionStrict.Off Then - WriteValue(builder, CompilationOptionNames.Strict, Options.OptionStrict.ToString()) + If Not String.IsNullOrEmpty(Options.RootNamespace) Then + WriteValue(builder, CompilationOptionNames.RootNamespace, Options.RootNamespace) End If If Options.ParseOptions IsNot Nothing Then - Dim preprocessorStrings = Options.ParseOptions.PreprocessorSymbols.Select(Function(p) - If (p.Value Is Nothing) Then - Return p.Key - End If - - Return p.Key + "=" + p.Value.ToString() - End Function) + Dim preprocessorStrings = Options.ParseOptions.PreprocessorSymbols.Select( + Function(p) As String + If TypeOf p.Value Is String Then + Return p.Key + "=""" + p.Value.ToString() + """" + ElseIf p.Value Is Nothing Then + Return p.Key + Else + Return p.Key + "=" + p.Value.ToString() + End If + End Function) WriteValue(builder, CompilationOptionNames.Define, String.Join(",", preprocessorStrings)) End If End Sub diff --git a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb index 671df214c5b0d..29a305bf7c492 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb @@ -895,6 +895,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Get End Property + Private ReadOnly Property IMethodSymbol_MethodImplementationFlags As System.Reflection.MethodImplAttributes Implements IMethodSymbol.MethodImplementationFlags + Get + Return Me.ImplementationAttributes + End Get + End Property + Private ReadOnly Property IMethodSymbol_IsExtensionMethod As Boolean Implements IMethodSymbol.IsExtensionMethod Get Return Me.IsExtensionMethod diff --git a/src/Compilers/VisualBasic/Test/Emit/Microsoft.CodeAnalysis.VisualBasic.Emit.UnitTests.vbproj b/src/Compilers/VisualBasic/Test/Emit/Microsoft.CodeAnalysis.VisualBasic.Emit.UnitTests.vbproj index f31c1eeb62e5a..991366659f83d 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Microsoft.CodeAnalysis.VisualBasic.Emit.UnitTests.vbproj +++ b/src/Compilers/VisualBasic/Test/Emit/Microsoft.CodeAnalysis.VisualBasic.Emit.UnitTests.vbproj @@ -168,5 +168,5 @@ - + diff --git a/src/Compilers/VisualBasic/Test/Emit/PDB/VisualBasicDeterministicBuildCompilationTests.vb b/src/Compilers/VisualBasic/Test/Emit/PDB/VisualBasicDeterministicBuildCompilationTests.vb index 1223780ba8c0a..12a856433127e 100644 --- a/src/Compilers/VisualBasic/Test/Emit/PDB/VisualBasicDeterministicBuildCompilationTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/PDB/VisualBasicDeterministicBuildCompilationTests.vb @@ -33,7 +33,7 @@ Public Class VisualBasicDeterministicBuildCompilationTests "define", originalOptions.ParseOptions.PreprocessorSymbols, isDefault:=Function(v) v.IsEmpty, - toString:=Function(v) String.Join(",", v.Select(Function(p) If(p.Value IsNot Nothing, $"{p.Key}={p.Value}", p.Key)))) + toString:=Function(v) String.Join(",", v.Select(Function(p) If(p.Value IsNot Nothing, $"{p.Key}=""{p.Value}""", p.Key)))) End Sub Private Sub TestDeterministicCompilationVB(syntaxTrees As SyntaxTree(), compilationOptions As VisualBasicCompilationOptions, emitOptions As EmitOptions, ParamArray metadataReferences() As TestMetadataReferenceInfo) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Microsoft.CodeAnalysis.VisualBasic.Semantic.UnitTests.vbproj b/src/Compilers/VisualBasic/Test/Semantic/Microsoft.CodeAnalysis.VisualBasic.Semantic.UnitTests.vbproj index 0fed500079196..57ff244223cac 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Microsoft.CodeAnalysis.VisualBasic.Semantic.UnitTests.vbproj +++ b/src/Compilers/VisualBasic/Test/Semantic/Microsoft.CodeAnalysis.VisualBasic.Semantic.UnitTests.vbproj @@ -83,5 +83,5 @@ - + \ No newline at end of file diff --git a/src/Compilers/VisualBasic/Test/Symbol/Microsoft.CodeAnalysis.VisualBasic.Symbol.UnitTests.vbproj b/src/Compilers/VisualBasic/Test/Symbol/Microsoft.CodeAnalysis.VisualBasic.Symbol.UnitTests.vbproj index 84e8efbcdfad3..e08abf620eb6f 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/Microsoft.CodeAnalysis.VisualBasic.Symbol.UnitTests.vbproj +++ b/src/Compilers/VisualBasic/Test/Symbol/Microsoft.CodeAnalysis.VisualBasic.Symbol.UnitTests.vbproj @@ -32,5 +32,5 @@ - + \ No newline at end of file diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/MethodImplementationFlagsTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/MethodImplementationFlagsTests.vb new file mode 100644 index 0000000000000..4ff0d5b4862f8 --- /dev/null +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/MethodImplementationFlagsTests.vb @@ -0,0 +1,164 @@ +' 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. + +Imports System.Reflection +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols + +Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Symbols + + Public Class MethodImplementationFlagsTests + Inherits BasicTestBase + + + Public Sub TestInliningFlags() + Dim src = + + + Public Sub M_Aggressive() + End Sub + + + Public Sub M_NoInlining() + End Sub +End Class + ]]> + + + Dim validator As Action(Of ModuleSymbol) = Sub([module]) + Dim c = [module].GlobalNamespace.GetMember(Of NamedTypeSymbol)("C") + Dim aggressiveInliningMethod As IMethodSymbol = c.GetMember(Of MethodSymbol)("M_Aggressive") + Assert.Equal(MethodImplAttributes.AggressiveInlining, aggressiveInliningMethod.MethodImplementationFlags) + Dim noInliningMethod As IMethodSymbol = c.GetMember(Of MethodSymbol)("M_NoInlining") + Assert.Equal(MethodImplAttributes.NoInlining, noInliningMethod.MethodImplementationFlags) + End Sub + + CompileAndVerify(src, sourceSymbolValidator:=validator, symbolValidator:=validator, useLatestFramework:=True) + End Sub + + + Public Sub TestOptimizationFlags() + Dim src = + + ' Aggressive optimization + Public Sub M_Aggressive() + End Sub + + + Public Sub M_NoOptimization() + End Sub +End Class + ]]> + + + Dim validator As Action(Of ModuleSymbol) = Sub([module]) + Dim c = [module].GlobalNamespace.GetMember(Of NamedTypeSymbol)("C") + Dim aggressiveOptimizationMethod As IMethodSymbol = c.GetMember(Of MethodSymbol)("M_Aggressive") + Assert.Equal(CType(512, MethodImplAttributes), aggressiveOptimizationMethod.MethodImplementationFlags) + Dim noOptimizationMethod As IMethodSymbol = c.GetMember(Of MethodSymbol)("M_NoOptimization") + Assert.Equal(MethodImplAttributes.NoOptimization, noOptimizationMethod.MethodImplementationFlags) + End Sub + + CompileAndVerify(src, sourceSymbolValidator:=validator, symbolValidator:=validator) + End Sub + + + Public Sub TestMixingOptimizationWithInliningFlags() + Dim src = + + ' aggressive optimization and no inlining + Public Sub M_AggressiveOpt_NoInlining() + End Sub + + + Public Sub M_NoOpt_NoInlining() + End Sub + + ' aggressive optimization and aggressive inlining + Public Sub M_AggressiveOpt_AggressiveInlining() + End Sub + + + Public Sub M_NoOpt_AggressiveInlining() + End Sub +End Class + ]]> + + + Dim validator As Action(Of ModuleSymbol) = Sub([module]) + Dim c = [module].GlobalNamespace.GetMember(Of NamedTypeSymbol)("C") + Dim aggressiveOptNoInliningMethod As IMethodSymbol = c.GetMember(Of MethodSymbol)("M_AggressiveOpt_NoInlining") + Assert.Equal(CType(512, MethodImplAttributes) Or MethodImplAttributes.NoInlining, aggressiveOptNoInliningMethod.MethodImplementationFlags) + Dim noOptNoInliningMethod As IMethodSymbol = c.GetMember(Of MethodSymbol)("M_NoOpt_NoInlining") + Assert.Equal(MethodImplAttributes.NoOptimization Or MethodImplAttributes.NoInlining, noOptNoInliningMethod.MethodImplementationFlags) + Dim aggressiveOptAggressiveInliningMethod As IMethodSymbol = c.GetMember(Of MethodSymbol)("M_AggressiveOpt_AggressiveInlining") + Assert.Equal(CType(512, MethodImplAttributes) Or MethodImplAttributes.AggressiveInlining, aggressiveOptAggressiveInliningMethod.MethodImplementationFlags) + Dim noOptAggressiveInliningMethod As IMethodSymbol = c.GetMember(Of MethodSymbol)("M_NoOpt_AggressiveInlining") + Assert.Equal(MethodImplAttributes.NoOptimization Or MethodImplAttributes.AggressiveInlining, noOptAggressiveInliningMethod.MethodImplementationFlags) + End Sub + + CompileAndVerify(src, sourceSymbolValidator:=validator, symbolValidator:=validator, useLatestFramework:=True) + End Sub + + + Public Sub TestPreserveSigAndRuntimeFlags() + Dim src = + + + Public Sub M() + End Sub +End Class + ]]> + + + Dim validator As Action(Of ModuleSymbol) = Sub([module]) + Dim c = [module].GlobalNamespace.GetMember(Of NamedTypeSymbol)("C") + Dim method As IMethodSymbol = c.GetMember(Of MethodSymbol)("M") + Assert.Equal(MethodImplAttributes.PreserveSig Or MethodImplAttributes.Runtime, method.MethodImplementationFlags) + End Sub + + CompileAndVerify(src, sourceSymbolValidator:=validator, symbolValidator:=validator, verify:=Verification.Skipped) + End Sub + + + Public Sub TestNativeFlag() + Dim src = + + + Public Sub M() + End Sub +End Class + ]]> + + + Dim validator As Action(Of ModuleSymbol) = Sub([module]) + Dim c = [module].GlobalNamespace.GetMember(Of NamedTypeSymbol)("C") + Dim method As IMethodSymbol = c.GetMember(Of MethodSymbol)("M") + Assert.Equal(MethodImplAttributes.Native, method.MethodImplementationFlags) + End Sub + + CompileAndVerify(src, sourceSymbolValidator:=validator, symbolValidator:=validator, verify:=Verification.Skipped) + End Sub + End Class +End Namespace diff --git a/src/Dependencies/Collections/Internal/SegmentedArrayHelper.cs b/src/Dependencies/Collections/Internal/SegmentedArrayHelper.cs index a778d6e06ad74..da94baee92d68 100644 --- a/src/Dependencies/Collections/Internal/SegmentedArrayHelper.cs +++ b/src/Dependencies/Collections/Internal/SegmentedArrayHelper.cs @@ -4,6 +4,8 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace Microsoft.CodeAnalysis.Collections.Internal { @@ -14,6 +16,52 @@ internal static class SegmentedArrayHelper // Large value types may benefit from a smaller number. internal const int IntrosortSizeThreshold = 16; + /// + /// A combination of and + /// . + /// + [SuppressMessage("Documentation", "CA1200:Avoid using cref tags with a prefix", Justification = "The field is not supported in all compilation targets.")] + internal const MethodImplOptions FastPathMethodImplOptions = MethodImplOptions.AggressiveInlining | (MethodImplOptions)512; + + [MethodImpl(FastPathMethodImplOptions)] + internal static int GetSegmentSize() + { + if (Unsafe.SizeOf() == Unsafe.SizeOf()) + { + return ReferenceTypeSegmentHelper.SegmentSize; + } + else + { + return ValueTypeSegmentHelper.SegmentSize; + } + } + + [MethodImpl(FastPathMethodImplOptions)] + internal static int GetSegmentShift() + { + if (Unsafe.SizeOf() == Unsafe.SizeOf()) + { + return ReferenceTypeSegmentHelper.SegmentShift; + } + else + { + return ValueTypeSegmentHelper.SegmentShift; + } + } + + [MethodImpl(FastPathMethodImplOptions)] + internal static int GetOffsetMask() + { + if (Unsafe.SizeOf() == Unsafe.SizeOf()) + { + return ReferenceTypeSegmentHelper.OffsetMask; + } + else + { + return ValueTypeSegmentHelper.OffsetMask; + } + } + /// /// Calculates the maximum number of elements of size which can fit into an array /// which has the following characteristics: @@ -24,7 +72,7 @@ internal static class SegmentedArrayHelper /// /// The size of the elements in the array. /// The segment size to use for small object heap segmented arrays. - internal static int CalculateSegmentSize(int elementSize) + private static int CalculateSegmentSize(int elementSize) { // Default Large Object Heap size threshold // https://github.com/dotnet/runtime/blob/c9d69e38d0e54bea5d188593ef6c3b30139f3ab1/src/coreclr/src/gc/gc.h#L111 @@ -50,7 +98,7 @@ static int ArraySize(int elementSize, int segmentSize) /// /// The number of elements in each page of the segmented array. Must be a power of 2. /// The shift to apply to the absolute index to get the page index within a segmented array. - internal static int CalculateSegmentShift(int segmentSize) + private static int CalculateSegmentShift(int segmentSize) { var segmentShift = 0; while (0 != (segmentSize >>= 1)) @@ -67,10 +115,36 @@ internal static int CalculateSegmentShift(int segmentSize) /// /// The number of elements in each page of the segmented array. Must be a power of 2. /// The bit mask to obtain the index within a page from an absolute index within a segmented array. - internal static int CalculateOffsetMask(int segmentSize) + private static int CalculateOffsetMask(int segmentSize) { Debug.Assert(segmentSize == 1 || (segmentSize & (segmentSize - 1)) == 0, "Expected size of 1, or a power of 2"); return segmentSize - 1; } + + internal static class TestAccessor + { + public static int CalculateSegmentSize(int elementSize) + => SegmentedArrayHelper.CalculateSegmentSize(elementSize); + + public static int CalculateSegmentShift(int elementSize) + => SegmentedArrayHelper.CalculateSegmentShift(elementSize); + + public static int CalculateOffsetMask(int elementSize) + => SegmentedArrayHelper.CalculateOffsetMask(elementSize); + } + + private static class ReferenceTypeSegmentHelper + { + public static readonly int SegmentSize = CalculateSegmentSize(Unsafe.SizeOf()); + public static readonly int SegmentShift = CalculateSegmentShift(SegmentSize); + public static readonly int OffsetMask = CalculateOffsetMask(SegmentSize); + } + + private static class ValueTypeSegmentHelper + { + public static readonly int SegmentSize = CalculateSegmentSize(Unsafe.SizeOf()); + public static readonly int SegmentShift = CalculateSegmentShift(SegmentSize); + public static readonly int OffsetMask = CalculateOffsetMask(SegmentSize); + } } } diff --git a/src/Dependencies/Collections/SegmentedArray`1.cs b/src/Dependencies/Collections/SegmentedArray`1.cs index 970aba7cdfcec..ed6e06652d2d2 100644 --- a/src/Dependencies/Collections/SegmentedArray`1.cs +++ b/src/Dependencies/Collections/SegmentedArray`1.cs @@ -30,17 +30,38 @@ namespace Microsoft.CodeAnalysis.Collections /// including any padding the implementation chooses to add. Specifically, array elements lie sizeof /// bytes apart. /// - private static readonly int s_segmentSize = SegmentedArrayHelper.CalculateSegmentSize(Unsafe.SizeOf()); + private static int SegmentSize + { + [MethodImpl(SegmentedArrayHelper.FastPathMethodImplOptions)] + get + { + return SegmentedArrayHelper.GetSegmentSize(); + } + } /// /// The bit shift to apply to an array index to get the page index within . /// - private static readonly int s_segmentShift = SegmentedArrayHelper.CalculateSegmentShift(s_segmentSize); + private static int SegmentShift + { + [MethodImpl(SegmentedArrayHelper.FastPathMethodImplOptions)] + get + { + return SegmentedArrayHelper.GetSegmentShift(); + } + } /// /// The bit mask to apply to an array index to get the index within a page of . /// - private static readonly int s_offsetMask = SegmentedArrayHelper.CalculateOffsetMask(s_segmentSize); + private static int OffsetMask + { + [MethodImpl(SegmentedArrayHelper.FastPathMethodImplOptions)] + get + { + return SegmentedArrayHelper.GetOffsetMask(); + } + } private readonly int _length; private readonly T[][] _items; @@ -59,17 +80,17 @@ public SegmentedArray(int length) } else { - _items = new T[(length + s_segmentSize - 1) >> s_segmentShift][]; + _items = new T[(length + SegmentSize - 1) >> SegmentShift][]; for (var i = 0; i < _items.Length - 1; i++) { - _items[i] = new T[s_segmentSize]; + _items[i] = new T[SegmentSize]; } // Make sure the last page only contains the number of elements required for the desired length. This // collection is not resizeable so any additional padding would be a waste of space. // // Avoid using (length & s_offsetMask) because it doesn't handle a last page size of s_segmentSize. - var lastPageSize = length - ((_items.Length - 1) << s_segmentShift); + var lastPageSize = length - ((_items.Length - 1) << SegmentShift); _items[_items.Length - 1] = new T[lastPageSize]; _length = length; @@ -94,9 +115,10 @@ private SegmentedArray(int length, T[][] items) public ref T this[int index] { + [MethodImpl(SegmentedArrayHelper.FastPathMethodImplOptions)] get { - return ref _items[index >> s_segmentShift][index & s_offsetMask]; + return ref _items[index >> SegmentShift][index & OffsetMask]; } } @@ -135,7 +157,7 @@ public void CopyTo(Array array, int index) { for (var i = 0; i < _items.Length; i++) { - _items[i].CopyTo(array, index + (i * s_segmentSize)); + _items[i].CopyTo(array, index + (i * SegmentSize)); } } @@ -144,7 +166,7 @@ void ICollection.CopyTo(T[] array, int arrayIndex) for (var i = 0; i < _items.Length; i++) { ICollection collection = _items[i]; - collection.CopyTo(array, arrayIndex + (i * s_segmentSize)); + collection.CopyTo(array, arrayIndex + (i * SegmentSize)); } } @@ -207,7 +229,7 @@ int IList.IndexOf(object? value) var index = list.IndexOf(value); if (index >= 0) { - return index + i * s_segmentSize; + return index + i * SegmentSize; } } @@ -222,7 +244,7 @@ int IList.IndexOf(T value) var index = list.IndexOf(value); if (index >= 0) { - return index + i * s_segmentSize; + return index + i * SegmentSize; } } @@ -393,7 +415,7 @@ public TestAccessor(SegmentedArray array) _array = array; } - public static int SegmentSize => s_segmentSize; + public static int SegmentSize => SegmentedArray.SegmentSize; public T[][] Items => _array._items; } diff --git a/src/EditorFeatures/CSharp.Wpf/CSharpEditorWpfResources.cs b/src/EditorFeatures/CSharp.Wpf/CSharpEditorWpfResources.cs deleted file mode 100644 index 30edc5256e4af..0000000000000 --- a/src/EditorFeatures/CSharp.Wpf/CSharpEditorWpfResources.cs +++ /dev/null @@ -1,15 +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 - -namespace Microsoft.CodeAnalysis.Editor.CSharp -{ - /// - /// Stub type - replace with type generated from resx file when resources are needed in this assembly. - /// - internal static class CSharpEditorWpfResources - { - } -} diff --git a/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj b/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj deleted file mode 100644 index a4754d0868f7f..0000000000000 --- a/src/EditorFeatures/CSharp.Wpf/Microsoft.CodeAnalysis.CSharp.EditorFeatures.Wpf.csproj +++ /dev/null @@ -1,49 +0,0 @@ - - - - - Library - Microsoft.CodeAnalysis.Editor.CSharp - net472 - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/EditorFeatures/CSharp.Wpf/PublicAPI.Shipped.txt b/src/EditorFeatures/CSharp.Wpf/PublicAPI.Shipped.txt deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/EditorFeatures/CSharp.Wpf/PublicAPI.Unshipped.txt b/src/EditorFeatures/CSharp.Wpf/PublicAPI.Unshipped.txt deleted file mode 100644 index 8b137891791fe..0000000000000 --- a/src/EditorFeatures/CSharp.Wpf/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs index 3e374089a2f69..711a0f99440e6 100644 --- a/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs +++ b/src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs @@ -748,6 +748,7 @@ private static bool ShouldRemoveBraceForObjectCreationExpression(ObjectCreationE // event EventHandler Bar {add; remove;} private static bool ShouldRemoveBraceForAccessorDeclaration(AccessorDeclarationSyntax accessorDeclarationNode, int caretPosition) => accessorDeclarationNode.Body != null + && accessorDeclarationNode.Body.Statements.IsEmpty() && accessorDeclarationNode.ExpressionBody == null && accessorDeclarationNode.Parent != null && accessorDeclarationNode.Parent.IsParentKind(SyntaxKind.PropertyDeclaration) diff --git a/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLineEnderTests.cs b/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLineEnderTests.cs index e867208baf073..b051ac686ddfe 100644 --- a/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLineEnderTests.cs +++ b/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticLineEnderTests.cs @@ -1258,6 +1258,74 @@ public int this[int i] }"); } + [WpfFact] + public void TestNonEmptyGetAccessor() + { + Test(@" +public Class Bar +{ + public int P + { + get + { + if (true) + $$ + { + return 1; + } + } + } +}", + @" +public Class Bar +{ + public int P + { + get + { + i$$f ($$true$$)$$ + { + return 1; + } + } + } +}"); + } + + [WpfFact] + public void TestNonEmptySetAccessor() + { + Test(@" +public Class Bar +{ + public int P + { + get; + set + { + if (true) + $$ + { + } + } + } +}", + @" +public Class Bar +{ + public int P + { + get; + set + { + i$$f (t$$rue)$$ + { + } + } + } +}"); + } + [WpfFact] public void TestSetAccessorOfIndexer() { @@ -1470,6 +1538,27 @@ public class Bar }"); } + [WpfFact] + public void TestNonEmptyProperty() + { + Test(@" +public class Bar +{ + public int Foo + { + get { } + $$ + } +}", @" +public class Bar +{ + public int Foo + { + $$get$$ { }$$ + } +}"); + } + [WpfFact] public void TestMulitpleFields() { @@ -1531,6 +1620,29 @@ public class Bar Test(secondResult, firstResult); } + [WpfFact] + public void TestNonEmptyEvent() + { + Test(@" +using System; +public class Bar +{ + public event EventHandler Foo + { + add { } + $$ + } +}", @" +using System; +public class Bar +{ + public event EventHandler Foo + { + $$add$$ {$$ }$$ + } +}"); + } + [WpfFact] public void TestObjectCreationExpressionWithParenthesis() { @@ -1808,6 +1920,40 @@ public class Foo Test(secondResult, firstResult); } + [WpfFact] + public void TestObjectCreationExpressionWithNonEmptyInitializer() + { + Test( + @" +public class Bar +{ + public void M() + { + var a = new Foo() { HH = 1, PP = 2 }; + $$ + } +} +public class Foo +{ + public int HH { get; set; } + public int PP { get; set; } +}", + @" +public class Bar +{ + public void M() + { + var a = n$$ew Fo$$o($$) {$$ HH = 1$$, PP = 2 $$}; + } +} +public class Foo +{ + public int HH { get; set; } + public int PP { get; set; } +}"); + + } + [WpfFact] public void TestIfStatementWithInnerStatement() { diff --git a/src/EditorFeatures/CSharpTest/CommentSelection/CSharpToggleLineCommentCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/CommentSelection/CSharpToggleLineCommentCommandHandlerTests.cs index 33058b624083b..8aeecd61b933c 100644 --- a/src/EditorFeatures/CSharpTest/CommentSelection/CSharpToggleLineCommentCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/CommentSelection/CSharpToggleLineCommentCommandHandlerTests.cs @@ -56,7 +56,7 @@ class C { void M() { -[| //var i = 1;|] + //var[||] i = 1; } }"; @@ -81,7 +81,7 @@ class C { void M() { -[| //var i = 1;|] +[||] //var i = 1; } }"; @@ -106,7 +106,7 @@ class C { void M() { -[| //var i = 1;|] + [|//var i = 1;|] } }"; @@ -131,7 +131,7 @@ class C { void M() { -[| //var i = 1;|] + //var [|i = 1;|] } }"; @@ -185,7 +185,7 @@ class C { void M() { -[| //var i = 1; // A comment.|] + //var i = 1; // A [|comment|]. } }"; @@ -210,7 +210,7 @@ class C { void M() { -[| //var i = 1; // A comment.|] + //var i = 1; [|// A comment.|] } }"; @@ -235,7 +235,7 @@ class C { void M() { -[| //var i = 1; // A comment.|] + [|//var i = 1; // A comment.|] } }"; @@ -260,7 +260,7 @@ class C { void M() { -[| //var i = 1; // A comment.|] + //var [||]i = 1; // A comment. } }"; @@ -285,7 +285,7 @@ class C { void M() { -[| //var i = 1; // A comment.|] + //var i = 1; // A [||]comment. } }"; @@ -310,7 +310,7 @@ class C { void M() { -[| //string s = '\\';|] + [|//string s = '\\';|] } }"; @@ -336,7 +336,7 @@ class C { void M() { -[| //var i = 1; + [|//var i = 1; //var j = 2;|] } }"; @@ -396,7 +396,7 @@ class C { void M() { -[| ////var i = 1; + [|////var i = 1; //var j = 2;|] } }"; @@ -423,7 +423,7 @@ class C { void M() { -[| //var i = 1; // A comment. + [|//var i = 1; // A comment. //var j = 2;|] } }"; @@ -479,8 +479,8 @@ class C { void M() { -[| //var i = 1;|] -[| //var j = 2;|] + //var [||]i = 1; + //var [||]j = 2; } }"; @@ -506,8 +506,8 @@ class C { void M() { -[| //var i = 1;|] -[| //var j = 2;|] + [|//var i = 1;|] + [|//var j = 2;|] } }"; @@ -523,7 +523,7 @@ class C { void M() { - [|//var i = 1;|] + [|//var i = |]1; [|var j = 2;|] } }"; @@ -533,8 +533,8 @@ class C { void M() { -[| ////var i = 1;|] -[| //var j = 2;|] + [|////var i = |]1; + [|//var j = 2;|] } }"; @@ -559,7 +559,7 @@ void M() { void M() { -[| //var i = 1;|] + [|//var i = 1;|] } }"; @@ -584,7 +584,7 @@ class C { void M() { -[| var i = 1;|] + var[||] i = 1; } }"; @@ -609,7 +609,7 @@ class C { void M() { -[| var i = 1;|] + [||] var i = 1; } }"; @@ -634,7 +634,7 @@ class C { void M() { -[| var i = 1; // A comment.|] + var i = 1; // A [||]comment. } }"; @@ -659,7 +659,7 @@ class C { void M() { -[| //var i = 1;|] + //var[||] i = 1; } }"; @@ -684,7 +684,7 @@ class C { void M() { -[| var i = 1;|] + [|var i = 1;|] } }"; @@ -709,7 +709,7 @@ class C { void M() { -[| var i = 1;|] + var [|i = 1;|] } }"; @@ -763,7 +763,7 @@ class C { void M() { -[| string s = '\\';|] + [|string s = '\\';|] } }"; @@ -789,7 +789,7 @@ class C { void M() { -[| var i = 1; + [|var i = 1; var j = 2;|] } }"; @@ -849,7 +849,7 @@ class C { void M() { -[| var i = 1; // A comment. + [|var i = 1; // A comment. var j = 2;|] } }"; @@ -906,9 +906,9 @@ class C { void M() { -[| var i = 1;|] + var [||]i = 1; [||] -[| var j = 2;|] + var [||]j = 2; } }"; @@ -935,9 +935,9 @@ class C { void M() { -[| var i = 1;|] + [|var i = 1;|] [||] -[| var j = 2;|] + [|var j = 2;|] } }"; @@ -962,7 +962,7 @@ void M() { void M() { -[| var i = 1;|] + [|var i = 1;|] } }"; @@ -990,7 +990,7 @@ class C { void M() { -[| ////var i = 1; + [|////var i = 1; //var j = 2;|] } @@ -1000,7 +1000,7 @@ class C { void M() { -[| //var i = 1; + [|//var i = 1; var j = 2;|] } @@ -1019,7 +1019,7 @@ class C { void M() { - [|//var i = 1;|] + [|//var i = |]1; [||] [|var j = 2;|] } @@ -1031,9 +1031,9 @@ class C { void M() { -[| ////var i = 1;|] + [|////var i = |]1; [||] -[| //var j = 2;|] + [|//var j = 2;|] } }", @" @@ -1041,9 +1041,9 @@ class C { void M() { -[| //var i = 1;|] + [|//var i = |]1; [||] -[| var j = 2;|] + [|var j = 2;|] } }" }; diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs index cfb3d808f9c38..3ca7347fd4759 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.cs @@ -34,13 +34,16 @@ public class ExtensionMethodImportCompletionProviderTests : AbstractCSharpComple private bool HideAdvancedMembers { get; set; } + private bool UsePartialSemantic { get; set; } = false; + protected override OptionSet WithChangedOptions(OptionSet options) { return options .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, ShowImportCompletionItemsOptionValue) .WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion) .WithChangedOption(CompletionOptions.HideAdvancedMembers, LanguageNames.CSharp, HideAdvancedMembers) - .WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForExtensionMethodImportCompletion, TimeoutInMilliseconds); + .WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForExtensionMethodImportCompletion, TimeoutInMilliseconds) + .WithChangedOption(CompletionServiceOptions.UsePartialSemanticForImportCompletion, UsePartialSemantic); } protected override TestComposition GetComposition() diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs index 808c679e06f5c..61cf9343de22a 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.cs @@ -33,13 +33,16 @@ internal override Type GetCompletionProviderType() private bool HideAdvancedMembers { get; set; } + private bool UsePartialSemantic { get; set; } = false; + protected override OptionSet WithChangedOptions(OptionSet options) { return options .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, ShowImportCompletionItemsOptionValue) .WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion) .WithChangedOption(CompletionServiceOptions.DisallowAddingImports, DisallowAddingImports) - .WithChangedOption(CompletionOptions.HideAdvancedMembers, LanguageNames.CSharp, HideAdvancedMembers); + .WithChangedOption(CompletionOptions.HideAdvancedMembers, LanguageNames.CSharp, HideAdvancedMembers) + .WithChangedOption(CompletionServiceOptions.UsePartialSemanticForImportCompletion, UsePartialSemantic); } protected override TestComposition GetComposition() diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.cs index 035215aa090ab..7b579419d8e81 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.cs @@ -458,9 +458,11 @@ int Test() }"; var expected = -@"class Program +@"using System.Threading.Tasks; + +class Program { - async System.Threading.Tasks.Task TestAsync() + async Task TestAsync() { await Task.Delay(1); } @@ -481,9 +483,11 @@ Program Test() }"; var expected = -@"class Program +@"using System.Threading.Tasks; + +class Program { - async System.Threading.Tasks.Task TestAsync() + async Task TestAsync() { await Task.Delay(1); } @@ -1199,9 +1203,11 @@ void M() } } }", -@"class C +@"using System.Threading.Tasks; + +class C { - async System.Threading.Tasks.Task MAsync() + async Task MAsync() { await using (var x = new object()) { @@ -1238,9 +1244,11 @@ void M() } } }", -@"class C +@"using System.Threading.Tasks; + +class C { - async System.Threading.Tasks.Task MAsync() + async Task MAsync() { await foreach (var n in new int[] { }) { @@ -1277,9 +1285,11 @@ void M() } } }", -@"class C +@"using System.Threading.Tasks; + +class C { - async System.Threading.Tasks.Task MAsync() + async Task MAsync() { await foreach (var (a, b) in new(int, int)[] { }) { diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs index ac43c9300e32e..46d7151fa5d2d 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs @@ -7,11 +7,13 @@ using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { + [UseExportProvider] public class ActiveStatementTests_Methods : EditingTestBase { #region Methods @@ -43,12 +45,17 @@ static void Main(string[] args) } "; - // TODO (bug 755959): better deleted active statement span var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.method)); + EditAndContinueValidation.VerifySemantics( + new[] { edits }, + new[] + { + DocumentResults( + active, + diagnostics: new[] { Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "Goo(int)")) }) + }); } [Fact] @@ -550,7 +557,7 @@ class C var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "get")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "get", FeaturesResources.code)); } [Fact] @@ -575,7 +582,7 @@ public void Property_BlockBodyToExpressionBody2() var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "int P", CSharpFeaturesResources.property_setter)); + Diagnostic(RudeEditKind.Delete, "int P", DeletedSymbolDisplay(CSharpFeaturesResources.property_setter, "P.set"))); } [Fact] @@ -601,7 +608,7 @@ class C // Can be improved with https://github.com/dotnet/roslyn/issues/22696 edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "int P")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "int P", FeaturesResources.code)); } #endregion @@ -654,7 +661,7 @@ public void Indexer_BlockBodyToExpressionBody2() var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "int this[int a]", CSharpFeaturesResources.indexer_setter)); + Diagnostic(RudeEditKind.Delete, "int this[int a]", DeletedSymbolDisplay(CSharpFeaturesResources.indexer_setter, "this[int].set"))); } [Fact] @@ -756,9 +763,10 @@ public T this[int i] var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); + // Rude edits of active statements (AS:1) are not reported if the top-level edits are rude. edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ActiveStatementUpdate, @"stringCollection[1] = ""hello"";"), - Diagnostic(RudeEditKind.GenericTypeUpdate, "set", CSharpFeaturesResources.indexer_setter)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "set", CSharpFeaturesResources.indexer_setter), + Diagnostic(RudeEditKind.ActiveStatementUpdate, "stringCollection[1] = \"hello\";")); } [Fact] @@ -858,9 +866,10 @@ public T this[int i] var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); + // Rude edits of active statements (AS:1) are not reported if the top-level edits are rude. edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ActiveStatementUpdate, "Console.WriteLine(stringCollection[1]);"), - Diagnostic(RudeEditKind.GenericTypeUpdate, "get", CSharpFeaturesResources.indexer_getter)); + Diagnostic(RudeEditKind.GenericTypeUpdate, "get", CSharpFeaturesResources.indexer_getter), + Diagnostic(RudeEditKind.ActiveStatementUpdate, "Console.WriteLine(stringCollection[1]);")); } [Fact] @@ -959,7 +968,7 @@ public T this[int i] var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "{")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code)); } [Fact] @@ -1058,7 +1067,7 @@ public T this[int i] var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "{")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code)); } #endregion diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 2fc405947deb6..3f5d01ca493bf 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -9,11 +9,15 @@ using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.CSharp.UnitTests; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { + [UseExportProvider] public class ActiveStatementTests : EditingTestBase { #region Update @@ -240,7 +244,7 @@ static void Goo(int a) var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "{")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code)); } // TODO (tomat): considering a change @@ -365,20 +369,20 @@ static void Goo(int a) var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "{"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "{"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "{"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "case 2:"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "default:"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "{"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "{"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "while (true)"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "do"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "for (int i = 0; i < 10; i++ )"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "foreach (var i in new[] { 1, 2 })"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "using ( var z = new C() )"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "fixed ( char* p = \"s\" )"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "label")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "case 2:", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "default:", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "while (true)", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "do", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "for (int i = 0; i < 10; i++ )", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "foreach (var i in new[] { 1, 2 })", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "using ( var z = new C() )", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "fixed ( char* p = \"s\" )", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "label", FeaturesResources.code)); } [Fact] @@ -588,7 +592,7 @@ static void Goo(int a) var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "{")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code)); } [WorkItem(755959, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/755959")] @@ -648,7 +652,8 @@ static void Main(String[] args) var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, null, FeaturesResources.namespace_)); + Diagnostic(RudeEditKind.Delete, null, FeaturesResources.namespace_), + Diagnostic(RudeEditKind.Delete, null, DeletedSymbolDisplay(FeaturesResources.class_, "N.C"))); } #endregion @@ -1310,6 +1315,19 @@ class C edits.VerifyRudeDiagnostics(active); } + [Fact] + public void InstanceConstructor_DeleteParameterless() + { + var src1 = "partial class C { public C() { System.Console.WriteLine(1); } }"; + var src2 = "partial class C { }"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.DeleteActiveStatement, "partial class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + } + #endregion #region Field and Property Initializers @@ -2087,8 +2105,8 @@ public C() {} var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.field)); + edits.VerifySemanticDiagnostics(active, + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.field, "a"))); } [Fact] @@ -2221,9 +2239,9 @@ public C() {} var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, + edits.VerifySemanticDiagnostics(active, Diagnostic(RudeEditKind.Move, "int c", FeaturesResources.field), - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.field)); + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.field, "a"))); } [Fact] @@ -2272,8 +2290,8 @@ class C var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.field)); + edits.VerifySemanticDiagnostics(active, + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.field, "a"))); } [Fact] @@ -2294,8 +2312,8 @@ class C var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.auto_property)); + edits.VerifySemanticDiagnostics(active, + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.auto_property, "a"))); } #endregion @@ -4292,7 +4310,7 @@ static void Main(string[] args) var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "for (; i < 10 ; i++)")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "for (; i < 10 ; i++)", FeaturesResources.code)); } [Fact] @@ -4470,7 +4488,7 @@ static void Main(string[] args) var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "for (int i = 1; ; i++ )")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "for (int i = 1; ; i++ )", FeaturesResources.code)); } [Fact] @@ -6077,12 +6095,12 @@ class C { public static int Main() { - return F() switch + return F() switch { int a when F1() => F2(), bool b => F3(), _ => F4() - }; + }; } }"; @@ -6172,18 +6190,17 @@ public void SwitchExpression_NestedInGoverningExpression() var src1 = @" class C { - public static int Main() => (F() switch { 0 => 1, _ => 2 }) switch { 1 => 10, _ => 20 }; + public static int Main() => (F() switch { 0 => 1, _ => 2 }) switch { 1 => 10, _ => 20 }; }"; var src2 = @" class C { - public static int Main() => (G() switch { 0 => 10, _ => 20 }) switch { 10 => 100, _ => 200 }; + public static int Main() => (G() switch { 0 => 10, _ => 20 }) switch { 10 => 100, _ => 200 }; }"; var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ActiveStatementUpdate, "switch { 0 => 10, _ => 20 }"), Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method), Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); } @@ -6704,7 +6721,7 @@ static void Main() var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "{")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code)); } [Fact] @@ -6786,7 +6803,7 @@ static void Main() var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "{")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code)); } [Fact] @@ -8488,18 +8505,20 @@ static void Main(string[] args) public void Lambdas_ExpressionToDelegate() { var src1 = @" +using System; class C { - static void Main(string[] args) + static void Main() { Func f = a => 1; } } "; var src2 = @" +using System; class C { - static void Main(string[] args) + static void Main() { Func f = delegate(int a) { return 1; }; } @@ -8542,18 +8561,20 @@ static void Main(string[] args) public void Lambdas_DelegateToExpression() { var src1 = @" +using System; class C { - static void Main(string[] args) + static void Main() { Func f = delegate(int a) { return 1; }; } } "; var src2 = @" +using System; class C { - static void Main(string[] args) + static void Main() { Func f = a => 1; } @@ -8569,18 +8590,20 @@ static void Main(string[] args) public void Lambdas_StatementsToDelegate() { var src1 = @" +using System; class C { - static void Main(string[] args) + static void Main() { Func f = a => { return 1; }; } } "; var src2 = @" +using System; class C { - static void Main(string[] args) + static void Main() { Func f = delegate(int a) { return 2; }; } @@ -9547,6 +9570,9 @@ static async void F() public void LambdaToAsyncLambda_WithActiveStatement() { var src1 = @" +using System; +using System.Threading.Tasks; + class C { static void F() @@ -9560,6 +9586,9 @@ static void F() } "; var src2 = @" +using System; +using System.Threading.Tasks; + class C { static void F() @@ -9583,6 +9612,8 @@ static void F() public void LambdaToAsyncLambda_WithActiveStatement_NoAwait() { var src1 = @" +using System; + class C { static void F() @@ -9592,6 +9623,8 @@ static void F() } "; var src2 = @" +using System; + class C { static void F() @@ -9705,20 +9738,24 @@ static void F() public void AnonymousFunctionToAsyncAnonymousFunction_WithActiveStatement_NoAwait() { var src1 = @" +using System.Threading.Tasks; + class C { static void F() { - var f = new Action(delegate() { Console.WriteLine(1); }); + var f = new Func(delegate() { Console.WriteLine(1); return Task.CompletedTask; }); } } "; var src2 = @" +using System.Threading.Tasks; + class C { static async void F() { - var f = new Action(async delegate() { Console.WriteLine(1); }); + var f = new Func(async delegate() { Console.WriteLine(1); }); } } "; @@ -10495,6 +10532,62 @@ static void F() #endregion + #region Partial Types + + [Fact] + public void InsertDeleteMethod_Inactive() + { + // Moving inactive method declaration in a file with active statements. + + var srcA1 = "partial class C { void F1() { System.Console.WriteLine(1); } }"; + var srcB1 = "partial class C { void F2() { } }"; + var srcA2 = "partial class C { void F1() { System.Console.WriteLine(1); } void F2() { } }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + activeStatements: GetActiveStatements(srcA1, srcA2), + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F2")), + }), + DocumentResults( + activeStatements: GetActiveStatements(srcB1, srcB2)) + }); + } + + [Fact, WorkItem(51177, "https://github.com/dotnet/roslyn/issues/51177")] + public void InsertDeleteMethod_Active() + { + // Moving active method declaration in a file with active statements. + // TODO: this is currently a rude edit + + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { void F() { System.Console.WriteLine(1); } }"; + var srcA2 = "partial class C { void F() { System.Console.WriteLine(1); } }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + activeStatements: GetActiveStatements(srcA1, srcA2), + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")), + }), + DocumentResults( + activeStatements: GetActiveStatements(srcB1, srcB2), + diagnostics: new[] { Diagnostic(RudeEditKind.DeleteActiveStatement, "partial class C", DeletedSymbolDisplay(FeaturesResources.method, "F()")) }) + }); + } + + #endregion + #region Misc [Fact] @@ -10516,8 +10609,8 @@ static void Goo(int a) var src2 = @""; var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, null, FeaturesResources.class_)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, null, DeletedSymbolDisplay(FeaturesResources.class_, "C"))); } [Fact] @@ -10587,7 +10680,7 @@ public static void F() active.OldStatements[0] = active.OldStatements[0].WithFlags(ActiveStatementFlags.PartiallyExecuted | ActiveStatementFlags.IsLeafFrame); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.PartiallyExecutedActiveStatementDelete, "{")); + Diagnostic(RudeEditKind.PartiallyExecutedActiveStatementDelete, "{", FeaturesResources.code)); } [Fact] @@ -10614,7 +10707,7 @@ public static void F() active.OldStatements[0] = active.OldStatements[0].WithFlags(ActiveStatementFlags.IsNonLeafFrame | ActiveStatementFlags.IsLeafFrame); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "{")); + Diagnostic(RudeEditKind.DeleteActiveStatement, "{", FeaturesResources.code)); } [Fact] @@ -10690,7 +10783,7 @@ public static void H(int x) var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - var validator = CSharpEditAndContinueTestHelpers.CreateInstance(node => + var validator = new CSharpEditAndContinueTestHelpers(faultInjector: node => { if (node.Parent is MethodDeclarationSyntax methodDecl && methodDecl.Identifier.Text == "G") { @@ -10702,7 +10795,10 @@ public static void H(int x) Diagnostic(RudeEditKind.MemberBodyTooBig, "public static void G()", FeaturesResources.method) : Diagnostic(RudeEditKind.MemberBodyInternalError, "public static void G()", FeaturesResources.method); - validator.VerifyRudeDiagnostics(edits, active, new[] { expectedDiagnostic }); + validator.VerifySemantics( + new[] { edits }, + TargetFramework.NetCoreApp, + new[] { DocumentResults(diagnostics: new[] { expectedDiagnostic }) }); } #endregion diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index e1d2f3da6f9ce..a6f68489a40e9 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -292,8 +292,8 @@ public static void Main() var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray.Empty, CancellationToken.None); Assert.True(result.HasChanges); - Assert.True(result.SemanticEdits[0].PreserveLocalVariables); var syntaxMap = result.SemanticEdits[0].SyntaxMap; + Assert.NotNull(syntaxMap); var newStatementSpan = result.ActiveStatements[0].Span; var newStatementTextSpan = newText.Lines.GetTextSpan(newStatementSpan); @@ -685,13 +685,16 @@ public static void Main() } "; var source2 = @" +class D +{ +} "; using var workspace = TestWorkspace.CreateCSharp(source1, composition: s_composition); - var oldProject = workspace.CurrentSolution.Projects.Single(); - var newDocId = DocumentId.CreateNewId(oldProject.Id); var oldSolution = workspace.CurrentSolution; + var oldProject = oldSolution.Projects.Single(); + var newDocId = DocumentId.CreateNewId(oldProject.Id); var newSolution = oldSolution.AddDocument(newDocId, "goo.cs", SourceText.From(source2)); workspace.TryApplyChanges(newSolution); @@ -715,8 +718,7 @@ public static void Main() } Assert.True(result.IsSingle()); - Assert.Equal(1, result.Single().RudeEditErrors.Count()); - Assert.Equal(RudeEditKind.InsertFile, result.Single().RudeEditErrors.Single().Kind); + Assert.Empty(result.Single().RudeEditErrors); } [Theory, CombinatorialData] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs index 802f37365d791..206cbef8f3b8c 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs @@ -5,39 +5,28 @@ #nullable disable using System; -using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Text; -using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { internal sealed class CSharpEditAndContinueTestHelpers : EditAndContinueTestHelpers { - private readonly ImmutableArray _fxReferences; private readonly CSharpEditAndContinueAnalyzer _analyzer; - public CSharpEditAndContinueTestHelpers(TargetFramework targetFramework, Action faultInjector = null) + public CSharpEditAndContinueTestHelpers(Action faultInjector = null) { - _fxReferences = TargetFrameworkUtil.GetReferences(targetFramework); _analyzer = new CSharpEditAndContinueAnalyzer(faultInjector); } - internal static CSharpEditAndContinueTestHelpers CreateInstance(Action faultInjector = null) - => new CSharpEditAndContinueTestHelpers(TargetFramework.Mscorlib46Extended, faultInjector); - - internal static CSharpEditAndContinueTestHelpers CreateInstance40(Action faultInjector = null) - => new CSharpEditAndContinueTestHelpers(TargetFramework.Mscorlib40AndSystemCore, faultInjector); - public override AbstractEditAndContinueAnalyzer Analyzer => _analyzer; - - public override Compilation CreateLibraryCompilation(string name, IEnumerable trees) - => CSharpCompilation.Create("New", trees, _fxReferences, TestOptions.UnsafeReleaseDll); + public override string LanguageName => LanguageNames.CSharp; + public override TreeComparer TopSyntaxComparer => SyntaxComparer.TopLevel; public override SyntaxTree ParseText(string source) => SyntaxFactory.ParseSyntaxTree(source, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview)); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index f4f6a74a1bea7..b45ad8c484eaa 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs @@ -21,7 +21,7 @@ internal static void VerifyUnchangedDocument( string source, ActiveStatementsDescription description) { - CSharpEditAndContinueTestHelpers.CreateInstance().VerifyUnchangedDocument( + new CSharpEditAndContinueTestHelpers().VerifyUnchangedDocument( ActiveStatementsDescription.ClearTags(source), description.OldStatements, description.NewSpans, @@ -40,7 +40,7 @@ internal static void VerifyRudeDiagnostics( ActiveStatementsDescription description, params RudeEditDiagnosticDescription[] expectedDiagnostics) { - CSharpEditAndContinueTestHelpers.CreateInstance().VerifyRudeDiagnostics( + VerifySemanticDiagnostics( editScript, description, expectedDiagnostics); @@ -52,7 +52,7 @@ internal static void VerifyLineEdits( IEnumerable expectedNodeUpdates, params RudeEditDiagnosticDescription[] expectedDiagnostics) { - CSharpEditAndContinueTestHelpers.CreateInstance().VerifyLineEdits( + new CSharpEditAndContinueTestHelpers().VerifyLineEdits( editScript, expectedLineEdits, expectedNodeUpdates, @@ -65,7 +65,17 @@ internal static void VerifySemanticDiagnostics( { VerifySemantics( new[] { editScript }, - expectedDiagnostics: expectedDiagnostics); + new[] { new DocumentAnalysisResultsDescription(diagnostics: expectedDiagnostics) }); + } + + internal static void VerifySemanticDiagnostics( + this EditScript editScript, + ActiveStatementsDescription activeStatements, + params RudeEditDiagnosticDescription[] expectedDiagnostics) + { + VerifySemantics( + new[] { editScript }, + new[] { new DocumentAnalysisResultsDescription(activeStatements: activeStatements, diagnostics: expectedDiagnostics) }); } internal static void VerifySemanticDiagnostics( @@ -75,8 +85,8 @@ internal static void VerifySemanticDiagnostics( { VerifySemantics( new[] { editScript }, - targetFrameworks: targetFrameworks, - expectedDiagnostics: expectedDiagnostics); + new[] { new DocumentAnalysisResultsDescription(diagnostics: expectedDiagnostics) }, + targetFrameworks: targetFrameworks); } internal static void VerifySemantics( @@ -86,25 +96,24 @@ internal static void VerifySemantics( { VerifySemantics( new[] { editScript }, - activeStatements, - expectedSemanticEdits: expectedSemanticEdits, - expectedDiagnostics: null); + new[] { new DocumentAnalysisResultsDescription(activeStatements, semanticEdits: expectedSemanticEdits) }); + } + + internal static void VerifySemantics( + this EditScript editScript, + params SemanticEditDescription[] expectedSemanticEdits) + { + VerifySemantics(editScript, ActiveStatementsDescription.Empty, expectedSemanticEdits); } internal static void VerifySemantics( - this EditScript[] editScripts, - ActiveStatementsDescription? activeStatements = null, - TargetFramework[]? targetFrameworks = null, - SemanticEditDescription[]? expectedSemanticEdits = null, - RudeEditDiagnosticDescription[]? expectedDiagnostics = null) + EditScript[] editScripts, + DocumentAnalysisResultsDescription[] expected, + TargetFramework[]? targetFrameworks = null) { foreach (var targetFramework in targetFrameworks ?? new[] { TargetFramework.NetStandard20, TargetFramework.NetCoreApp }) { - new CSharpEditAndContinueTestHelpers(targetFramework).VerifySemantics( - editScripts, - activeStatements, - expectedSemanticEdits, - expectedDiagnostics); + new CSharpEditAndContinueTestHelpers().VerifySemantics(editScripts, targetFramework, expected); } } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index 5bb9bdb679f3d..329d1b71c8b5f 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -2,16 +2,14 @@ // 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; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.EditAndContinue; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; @@ -39,17 +37,23 @@ internal enum MethodKind internal static RudeEditDiagnosticDescription Diagnostic(RudeEditKind rudeEditKind, string squiggle, params string[] arguments) => new(rudeEditKind, squiggle, arguments, firstLine: null); - internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func symbolProvider, IEnumerable> syntaxMap) - { - Assert.NotNull(syntaxMap); - return new SemanticEditDescription(kind, symbolProvider, syntaxMap, preserveLocalVariables: true); - } + internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func symbolProvider, IEnumerable>? syntaxMap, string? partialType = null) + => new(kind, symbolProvider, (partialType != null) ? c => c.GetMember(partialType) : null, syntaxMap, hasSyntaxMap: syntaxMap != null); + + internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func symbolProvider, string? partialType = null, bool preserveLocalVariables = false) + => new(kind, symbolProvider, (partialType != null) ? c => c.GetMember(partialType) : null, syntaxMap: null, preserveLocalVariables); + + internal static string DeletedSymbolDisplay(string kind, string displayName) + => string.Format(FeaturesResources.member_kind_and_name, kind, displayName); - internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func symbolProvider, bool preserveLocalVariables = false) - => new(kind, symbolProvider, syntaxMap: null, preserveLocalVariables); + internal static DocumentAnalysisResultsDescription DocumentResults( + ActiveStatementsDescription? activeStatements = null, + SemanticEditDescription[]? semanticEdits = null, + RudeEditDiagnosticDescription[]? diagnostics = null) + => new(activeStatements, semanticEdits, diagnostics); private static SyntaxTree ParseSource(string source) - => CSharpEditAndContinueTestHelpers.CreateInstance().ParseText(ActiveStatementsDescription.ClearTags(source)); + => new CSharpEditAndContinueTestHelpers().ParseText(ActiveStatementsDescription.ClearTags(source)); internal static EditScript GetTopEdits(string src1, string src2) { @@ -63,6 +67,14 @@ internal static EditScript GetTopEdits(string src1, string src2) return match.GetTreeEdits(); } + public static EditScript GetTopEdits(EditScript methodEdits) + { + var oldMethodSource = methodEdits.Match.OldRoot.ToFullString(); + var newMethodSource = methodEdits.Match.NewRoot.ToFullString(); + + return GetTopEdits(WrapMethodBodyWithClass(oldMethodSource), WrapMethodBodyWithClass(newMethodSource)); + } + /// /// Gets method edits on the current level of the source hierarchy. This means that edits on lower labeled levels of the hierarchy are not expected to be returned. /// @@ -103,6 +115,8 @@ public static MatchingPairs ToMatchingPairs(Match match) public static MatchingPairs ToMatchingPairs(IEnumerable> matches) => EditAndContinueTestHelpers.ToMatchingPairs(matches); +#nullable disable + internal static BlockSyntax MakeMethodBody( string bodySource, MethodKind kind = MethodKind.Regular) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs index 57e9be220784e..9e9d146525be9 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs @@ -9,10 +9,12 @@ using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { + [UseExportProvider] public class LineEditTests : EditingTestBase { #region Methods @@ -1423,5 +1425,44 @@ class C } #endregion + + #region Types + + [Fact] + public void Type_Reorder1() + { + var src1 = @" +class C +{ + static int F1() => 1; + static int F2() => 1; +} + +class D +{ + static int G1() => 1; + static int G2() => 1; +} +"; + var src2 = @" +class D +{ + static int G1() => 1; + static int G2() => 1; +} + +class C +{ + static int F1() => 1; + static int F2() => 1; +} +"; + var edits = GetTopEdits(src1, src2); + edits.VerifyLineEdits( + new[] { new SourceLineUpdate(3, 9), new SourceLineUpdate(4, 10), new SourceLineUpdate(9, 3), new SourceLineUpdate(10, 4) }, + Array.Empty()); + } + + #endregion } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 45411ddddb79c..e863592fd1c4e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -17,6 +17,7 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { + [UseExportProvider] public class StatementEditingTests : EditingTestBase { #region Strings @@ -6813,7 +6814,8 @@ public void LocalFunction_In_Parameter_InsertParameter() edits.VerifyEdits( "Update [void M() { void local() { throw null; } }]@13 -> [void M() { void local(in int b) { throw null; } }]@13"); - edits.VerifyRudeDiagnostics(); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingLambdaParameters, "local", CSharpFeaturesResources.local_function)); } [Fact] @@ -6827,7 +6829,8 @@ public void LocalFunction_In_Parameter_Update() edits.VerifyEdits( "Update [void M() { void local(int b) { throw null; } }]@13 -> [void M() { void local(in int b) { throw null; } }]@13"); - edits.VerifyRudeDiagnostics(); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingLambdaParameters, "local", CSharpFeaturesResources.local_function)); } [Fact] @@ -7000,9 +7003,8 @@ public void LocalFunction_AddAttribute() "Insert [A]@3"); // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "[A]", FeaturesResources.attribute)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "[A]", FeaturesResources.attribute)); } [Fact] @@ -7018,9 +7020,8 @@ public void LocalFunction_RemoveAttribute() "Delete [A]@3"); // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.attribute)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.attribute)); } [Fact] @@ -7034,9 +7035,7 @@ public void LocalFunction_ReorderAttribute() edits.VerifyEdits("Reorder [B]@6 -> @3"); // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(); + GetTopEdits(edits).VerifyRudeDiagnostics(); } [Fact] @@ -7054,9 +7053,7 @@ public void LocalFunction_CombineAttributeLists() "Delete [B]@6"); // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics( + GetTopEdits(edits).VerifyRudeDiagnostics( Diagnostic(RudeEditKind.Insert, "B", FeaturesResources.attribute), Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.attribute)); } @@ -7075,10 +7072,8 @@ public void LocalFunction_SplitAttributeLists() "Insert [B]@6", "Delete [B]@6"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "[B]", FeaturesResources.attribute)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "[B]", FeaturesResources.attribute)); } [Fact] @@ -7091,10 +7086,8 @@ public void LocalFunction_ChangeAttributeListTarget1() edits.VerifyEdits("Update [[return: A]]@2 -> [[A]]@2"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Update, "[A]", FeaturesResources.attribute)); } [Fact] @@ -7107,10 +7100,8 @@ public void LocalFunction_ChangeAttributeListTarget2() edits.VerifyEdits("Update [[A]]@2 -> [[return: A]]@2"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Update, "return:", CSharpFeaturesResources.attribute_target)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Update, "return:", CSharpFeaturesResources.attribute_target)); } [Fact] @@ -7125,10 +7116,8 @@ public void LocalFunction_ReturnType_AddAttribute() "Insert [[return: A]]@2", "Insert [A]@11"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "[return: A]", FeaturesResources.attribute)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "[return: A]", FeaturesResources.attribute)); } [Fact] @@ -7143,10 +7132,8 @@ public void LocalFunction_ReturnType_RemoveAttribute() "Delete [[return: A]]@2", "Delete [A]@11"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.attribute)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.attribute)); } [Fact] @@ -7159,10 +7146,7 @@ public void LocalFunction_ReturnType_ReorderAttribute() edits.VerifyEdits("Reorder [B]@14 -> @11"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(); + GetTopEdits(edits).VerifyRudeDiagnostics(); } [Fact] @@ -7177,10 +7161,8 @@ public void LocalFunction_Parameter_AddAttribute() "Insert [[A]]@9", "Insert [A]@10"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "[A]", FeaturesResources.attribute)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "[A]", FeaturesResources.attribute)); } [Fact] @@ -7195,10 +7177,8 @@ public void LocalFunction_Parameter_RemoveAttribute() "Delete [[A]]@9", "Delete [A]@10"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "int i", FeaturesResources.attribute)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "int i", FeaturesResources.attribute)); } [Fact] @@ -7211,10 +7191,7 @@ public void LocalFunction_Parameter_ReorderAttribute() edits.VerifyEdits("Reorder [B]@13 -> @10"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(); + GetTopEdits(edits).VerifyRudeDiagnostics(); } [Fact] @@ -7229,10 +7206,8 @@ public void LocalFunction_TypeParameter_AddAttribute() "Insert [[A]]@9", "Insert [A]@10"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "[A]", FeaturesResources.attribute)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "[A]", FeaturesResources.attribute)); } [Fact] @@ -7247,10 +7222,8 @@ public void LocalFunction_TypeParameter_RemoveAttribute() "Delete [[A]]@9", "Delete [A]@10"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "T", FeaturesResources.attribute)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "T", FeaturesResources.attribute)); } [Fact] @@ -7263,10 +7236,7 @@ public void LocalFunction_TypeParameter_ReorderAttribute() edits.VerifyEdits("Reorder [B]@13 -> @10"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(); + GetTopEdits(edits).VerifyRudeDiagnostics(); } [Fact] @@ -7280,10 +7250,8 @@ public void LocalFunctions_TypeParameter_Insert1() "Insert []@8", "Insert [A]@9"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "", FeaturesResources.type_parameter)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "", FeaturesResources.type_parameter)); } [Fact] @@ -7297,10 +7265,8 @@ public void LocalFunctions_TypeParameter_Insert2() "Update []@8 -> []@8", "Insert [B]@11"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Insert, "B", FeaturesResources.type_parameter)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "B", FeaturesResources.type_parameter)); } [Fact] @@ -7314,10 +7280,8 @@ public void LocalFunctions_TypeParameter_Delete1() "Delete []@8", "Delete [A]@9"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.type_parameter)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.type_parameter)); } [Fact] @@ -7331,10 +7295,8 @@ public void LocalFunctions_TypeParameter_Delete2() "Update []@8 -> []@8", "Delete [A]@9"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.type_parameter)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "L", FeaturesResources.type_parameter)); } [Fact] @@ -7347,10 +7309,8 @@ public void LocalFunctions_TypeParameter_Update() edits.VerifyEdits( "Update [A]@9 -> [B]@9"); - // Get top edits so we can validate rude edits - edits = GetTopEdits(WrapMethodBodyWithClass(src1), WrapMethodBodyWithClass(src2)); - - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Renamed, "B", FeaturesResources.type_parameter)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Renamed, "B", FeaturesResources.type_parameter)); } [Fact] @@ -7363,7 +7323,8 @@ public void LocalFunctions_TypeParameter_Reorder() edits.VerifyEdits( "Reorder [B]@11 -> @9"); - edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Move, "B", FeaturesResources.type_parameter)); + GetTopEdits(edits).VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Move, "B", FeaturesResources.type_parameter)); } [Fact] @@ -7377,10 +7338,11 @@ public void LocalFunctions_TypeParameter_ReorderAndUpdate() "Reorder [B]@11 -> @9", "Update [A]@9 -> [C]@11"); - edits.VerifyRudeDiagnostics( + GetTopEdits(edits).VerifyRudeDiagnostics( Diagnostic(RudeEditKind.Move, "B", FeaturesResources.type_parameter), Diagnostic(RudeEditKind.Renamed, "C", FeaturesResources.type_parameter)); } + #endregion #region Queries @@ -8908,13 +8870,9 @@ static IEnumerable F() "; var edits = GetTopEdits(src1, src2); - CSharpEditAndContinueTestHelpers.CreateInstance40().VerifySemantics( - new[] { edits }, - ActiveStatementsDescription.Empty, - expectedDiagnostics: new[] - { - Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "static IEnumerable F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute") - }); + edits.VerifySemanticDiagnostics( + targetFrameworks: new[] { TargetFramework.Mscorlib40AndSystemCore }, + Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "static IEnumerable F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute")); } [Fact] @@ -8944,7 +8902,8 @@ static IEnumerable F() "; var edits = GetTopEdits(src1, src2); - CSharpEditAndContinueTestHelpers.CreateInstance40().VerifySemantics(new[] { edits }); + edits.VerifySemanticDiagnostics( + targetFrameworks: new[] { TargetFramework.Mscorlib40AndSystemCore }); } #endregion diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxComparerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxComparerTests.cs index ee0f7ee51e5c2..95f06a78ca88c 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxComparerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxComparerTests.cs @@ -7,11 +7,13 @@ using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Differencing; +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { + [UseExportProvider] public class SyntaxComparerTests { private static SyntaxNode MakeLiteral(int n) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 0f56eb6ba6fba..80ab9bb7580e6 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -4,8 +4,8 @@ #nullable disable +using System; using System.Linq; -using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.EditAndContinue; @@ -17,6 +17,7 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { + [UseExportProvider] public class TopLevelEditingTests : EditingTestBase { #region Usings @@ -39,20 +40,21 @@ public void UsingDelete1() public void UsingDelete2() { var src1 = @" -using System.Diagnostics; +using D = System.Diagnostics; using System.Collections; using System.Collections.Generic; "; var src2 = @" -using System.Diagnostics; using System.Collections.Generic; "; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Delete [using System.Collections;]@29"); + "Delete [using D = System.Diagnostics;]@2", + "Delete [using System.Collections;]@33"); edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, null, CSharpFeaturesResources.using_directive), Diagnostic(RudeEditKind.Delete, null, CSharpFeaturesResources.using_directive)); } @@ -60,20 +62,21 @@ public void UsingDelete2() public void UsingInsert() { var src1 = @" -using System.Diagnostics; using System.Collections.Generic; "; var src2 = @" -using System.Diagnostics; +using D = System.Diagnostics; using System.Collections; using System.Collections.Generic; "; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Insert [using System.Collections;]@29"); + "Insert [using D = System.Diagnostics;]@2", + "Insert [using System.Collections;]@33"); edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "using D = System.Diagnostics;", CSharpFeaturesResources.using_directive), Diagnostic(RudeEditKind.Insert, "using System.Collections;", CSharpFeaturesResources.using_directive)); } @@ -217,6 +220,55 @@ namespace N #endregion + #region Extern Alias + + [Fact] + public void ExternAliasUpdate() + { + var src1 = "extern alias X;"; + var src2 = "extern alias Y;"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [extern alias X;]@0 -> [extern alias Y;]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Update, "extern alias Y;", CSharpFeaturesResources.extern_alias)); + } + + [Fact] + public void ExternAliasInsert() + { + var src1 = ""; + var src2 = "extern alias Y;"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert [extern alias Y;]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "extern alias Y;", CSharpFeaturesResources.extern_alias)); + } + + [Fact] + public void ExternAliasDelete() + { + var src1 = "extern alias Y;"; + var src2 = ""; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Delete [extern alias Y;]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, null, CSharpFeaturesResources.extern_alias)); + } + + #endregion + #region Attributes [Fact] @@ -735,6 +787,7 @@ public void InterfaceInsert() public interface I { void F(); + static void G() {} }"; var edits = GetTopEdits(src1, src2); @@ -867,7 +920,7 @@ public class SubClass : BaseClass, IConflict [WorkItem(37128, "https://github.com/dotnet/roslyn/issues/37128")] [Fact] - public void Interface_AddMembersWithImplementation() + public void Interface_InsertMembers() { var src1 = @" using System; @@ -915,42 +968,407 @@ event Action VirtualEvent { add { } remove { } } abstract event Action AbstractEvent; sealed event Action NonVirtualEvent { add { } remove { } } + abstract class C { } interface J { } + enum E { } + delegate void D(); } "; var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( + // TODO: InsertIntoInterface errors are reported due to https://github.com/dotnet/roslyn/issues/37128. + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertIntoInterface, "static void StaticMethod()", FeaturesResources.method), Diagnostic(RudeEditKind.InsertVirtual, "void VirtualMethod1()", FeaturesResources.method), Diagnostic(RudeEditKind.InsertVirtual, "virtual void VirtualMethod2()", FeaturesResources.method), Diagnostic(RudeEditKind.InsertVirtual, "abstract void AbstractMethod()", FeaturesResources.method), + Diagnostic(RudeEditKind.InsertIntoInterface, "sealed void NonVirtualMethod()", FeaturesResources.method), Diagnostic(RudeEditKind.InsertOperator, "public static int operator +(I a, I b)", FeaturesResources.operator_), + Diagnostic(RudeEditKind.InsertIntoInterface, "static int StaticProperty1", FeaturesResources.auto_property), + Diagnostic(RudeEditKind.InsertIntoInterface, "static int StaticProperty2", FeaturesResources.property_), Diagnostic(RudeEditKind.InsertVirtual, "virtual int VirtualProperty1", FeaturesResources.auto_property), Diagnostic(RudeEditKind.InsertVirtual, "virtual int VirtualProperty2", FeaturesResources.auto_property), Diagnostic(RudeEditKind.InsertVirtual, "int VirtualProperty3", FeaturesResources.auto_property), Diagnostic(RudeEditKind.InsertVirtual, "int VirtualProperty4", FeaturesResources.auto_property), Diagnostic(RudeEditKind.InsertVirtual, "abstract int AbstractProperty1", FeaturesResources.property_), Diagnostic(RudeEditKind.InsertVirtual, "abstract int AbstractProperty2", FeaturesResources.property_), + Diagnostic(RudeEditKind.InsertIntoInterface, "sealed int NonVirtualProperty", FeaturesResources.property_), Diagnostic(RudeEditKind.InsertVirtual, "int this[byte virtualIndexer]", FeaturesResources.indexer_), Diagnostic(RudeEditKind.InsertVirtual, "int this[sbyte virtualIndexer]", FeaturesResources.indexer_), Diagnostic(RudeEditKind.InsertVirtual, "virtual int this[ushort virtualIndexer]", FeaturesResources.indexer_), Diagnostic(RudeEditKind.InsertVirtual, "virtual int this[short virtualIndexer]", FeaturesResources.indexer_), Diagnostic(RudeEditKind.InsertVirtual, "abstract int this[uint abstractIndexer]", FeaturesResources.indexer_), Diagnostic(RudeEditKind.InsertVirtual, "abstract int this[int abstractIndexer]", FeaturesResources.indexer_), - Diagnostic(RudeEditKind.InsertVirtual, "event Action VirtualEvent", FeaturesResources.event_), - Diagnostic(RudeEditKind.InsertVirtual, "abstract event Action AbstractEvent", CSharpFeaturesResources.event_field), - // TODO: The following errors are reported due to https://github.com/dotnet/roslyn/issues/37128. - Diagnostic(RudeEditKind.InsertIntoInterface, "static int StaticField = 10", FeaturesResources.field), - Diagnostic(RudeEditKind.InsertIntoInterface, "static void StaticMethod()", FeaturesResources.method), - Diagnostic(RudeEditKind.InsertIntoInterface, "sealed void NonVirtualMethod()", FeaturesResources.method), - Diagnostic(RudeEditKind.InsertIntoInterface, "static int StaticProperty1", FeaturesResources.auto_property), - Diagnostic(RudeEditKind.InsertIntoInterface, "static int StaticProperty2", FeaturesResources.property_), - Diagnostic(RudeEditKind.InsertIntoInterface, "sealed int NonVirtualProperty", FeaturesResources.property_), Diagnostic(RudeEditKind.InsertIntoInterface, "sealed int this[ulong nonVirtualIndexer]", FeaturesResources.indexer_), Diagnostic(RudeEditKind.InsertIntoInterface, "sealed int this[long nonVirtualIndexer]", FeaturesResources.indexer_), - Diagnostic(RudeEditKind.InsertIntoInterface, "static event Action StaticEvent", CSharpFeaturesResources.event_field), Diagnostic(RudeEditKind.InsertIntoInterface, "static event Action StaticEvent2", FeaturesResources.event_), - Diagnostic(RudeEditKind.InsertIntoInterface, "sealed event Action NonVirtualEvent", FeaturesResources.event_)); + Diagnostic(RudeEditKind.InsertVirtual, "event Action VirtualEvent", FeaturesResources.event_), + Diagnostic(RudeEditKind.InsertIntoInterface, "sealed event Action NonVirtualEvent", FeaturesResources.event_), + Diagnostic(RudeEditKind.InsertIntoInterface, "StaticField = 10", FeaturesResources.field), + Diagnostic(RudeEditKind.InsertIntoInterface, "StaticEvent", CSharpFeaturesResources.event_field), + Diagnostic(RudeEditKind.InsertVirtual, "AbstractEvent", CSharpFeaturesResources.event_field)); + } + + [Fact] + public void Interface_InsertDelete() + { + var srcA1 = @" +interface I +{ + static void M() { } +} +"; + var srcB1 = @" +"; + + var srcA2 = @" +"; + var srcB2 = @" +interface I +{ + static void M() { } +} +"; + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("M")) + }), + }); + } + + [Fact] + public void GenericType_InsertMembers() + { + var src1 = @" +using System; +class C +{ +} +"; + var src2 = @" +using System; +class C +{ + void M() {} + int P1 { get; set; } + int P2 { get => 1; set {} } + int this[int i] { get => 1; set {} } + event Action E { add {} remove {} } + event Action EF; + int F1, F2; + + enum E {} + interface I {} + class D {} +} +"; + var edits = GetTopEdits(src1, src2); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertIntoGenericType, "void M()", FeaturesResources.method), + Diagnostic(RudeEditKind.InsertIntoGenericType, "int P1", FeaturesResources.auto_property), + Diagnostic(RudeEditKind.InsertIntoGenericType, "int P2", FeaturesResources.auto_property), + Diagnostic(RudeEditKind.InsertIntoGenericType, "int this[int i]", FeaturesResources.indexer_), + Diagnostic(RudeEditKind.InsertIntoGenericType, "event Action E", FeaturesResources.event_), + Diagnostic(RudeEditKind.InsertIntoGenericType, "EF", CSharpFeaturesResources.event_field), + Diagnostic(RudeEditKind.InsertIntoGenericType, "F1", FeaturesResources.field), + Diagnostic(RudeEditKind.InsertIntoGenericType, "F2", FeaturesResources.field)); + } + + [Fact] + public void Type_Delete() + { + var src1 = @" +class C { void F() {} } +struct S { void F() {} } +interface I { void F() {} } +"; + var src2 = ""; + + GetTopEdits(src1, src2).VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, null, DeletedSymbolDisplay(FeaturesResources.class_, "C")), + Diagnostic(RudeEditKind.Delete, null, DeletedSymbolDisplay(CSharpFeaturesResources.struct_, "S")), + Diagnostic(RudeEditKind.Delete, null, DeletedSymbolDisplay(FeaturesResources.interface_, "I"))); + } + + [Fact] + public void PartialType_Delete() + { + var srcA1 = "partial class C { void F() {} void M() { } }"; + var srcB1 = "partial class C { void G() {} }"; + var srcA2 = ""; + var srcB2 = "partial class C { void G() {} void M() { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.Delete, null, DeletedSymbolDisplay(FeaturesResources.method, "C.F()")) }), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("M")), + }) + }); + } + + [Fact] + public void PartialType_InsertFirstDeclaration() + { + var src1 = ""; + var src2 = "partial class C { void F() {} }"; + + GetTopEdits(src1, src2).VerifySemantics( + ActiveStatementsDescription.Empty, + new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C"), preserveLocalVariables: false) }); + } + + [Fact] + public void PartialType_InsertSecondDeclaration() + { + var srcA1 = "partial class C { void F() {} }"; + var srcB1 = ""; + var srcA2 = "partial class C { void F() {} }"; + var srcB2 = "partial class C { void G() {} }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").GetMember("G"), preserveLocalVariables: false) + }), + }); + } + + [Fact] + public void Type_DeleteInsert() + { + var srcA1 = @" +class C { void F() {} } +struct S { void F() {} } +interface I { void F() {} } +"; + var srcB1 = ""; + + var srcA2 = srcB1; + var srcB2 = srcA1; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("F")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("F")), + }) + }); + } + + [Fact] + public void GenericType_DeleteInsert() + { + var srcA1 = @" +class C { void F() {} } +struct S { void F() {} } +interface I { void F() {} } +"; + var srcB1 = ""; + + var srcA2 = srcB1; + var srcB2 = srcA1; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + diagnostics: new[] + { + Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), + Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), + Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), + }) + }); + } + + [Fact] + public void Type_DeleteInsert_NonInsertableMembers() + { + var srcA1 = @" +abstract class C +{ + public abstract void AbstractMethod(); + public virtual void VirtualMethod() {} + public override string ToString() => null; + public void I.G() {} +} + +interface I +{ + void G(); + void F() {} +} +"; + var srcB1 = ""; + + var srcA2 = srcB1; + var srcB2 = srcA1; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("VirtualMethod")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("ToString")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("I.G")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("F")), + }) + }); + } + + [Fact] + public void Type_DeleteInsert_DataMembers() + { + var srcA1 = @" +class C +{ + public int x = 1; + public int y = 2; + public int P { get; set; } = 3; + public event System.Action E = new System.Action(null); +} +"; + var srcB1 = ""; + + var srcA2 = ""; + var srcB2 = @" +class C +{ + public int x = 1; + public int y = 2; + public int P { get; set; } = 3; + public event System.Action E = new System.Action(null); +} +"; + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true), + }) + }); + } + + [Fact] + public void Type_DeleteInsert_DataMembers_PartialSplit() + { + var srcA1 = @" +class C +{ + public int x = 1; + public int y = 2; + public int P { get; set; } = 3; +} +"; + var srcB1 = ""; + + var srcA2 = @" +partial class C +{ + public int x = 1; + public int y = 2; +} +"; + var srcB2 = @" +partial class C +{ + public int P { get; set; } = 3; +} +"; + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true), + }) + }); + } + + [Fact] + public void Type_DeleteInsert_DataMembers_PartialMerge() + { + var srcA1 = @" +partial class C +{ + public int x = 1; + public int y = 2; +} +"; + var srcB1 = @" +partial class C +{ + public int P { get; set; } = 3; +}"; + + var srcA2 = @" +class C +{ + public int x = 1; + public int y = 2; + public int P { get; set; } = 3; +} +"; + + var srcB2 = @" +"; + // note that accessors are not updated since they do not have bodies + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true), + }), + + DocumentResults() + }); } #endregion @@ -1276,7 +1694,7 @@ public void EnumMemberDelete() "Delete [Blue]@18"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "enum Color", FeaturesResources.enum_value)); + Diagnostic(RudeEditKind.Delete, "enum Color", DeletedSymbolDisplay(FeaturesResources.enum_value, "Blue"))); } [Fact] @@ -1290,7 +1708,7 @@ public void EnumMemberDelete2() edits.VerifyEdits("Delete [Blue]@18"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "enum Color", FeaturesResources.enum_value)); + Diagnostic(RudeEditKind.Delete, "enum Color", DeletedSymbolDisplay(FeaturesResources.enum_value, "Blue"))); } [WorkItem(754916, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/754916"), WorkItem(793197, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/793197")] @@ -1433,8 +1851,8 @@ public void Delegates_Delete() "Delete [private delegate void D();]@10", "Delete [()]@33"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.delegate_)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.delegate_, "D"))); } [Fact] @@ -1542,6 +1960,21 @@ public void Delegates_Parameter_Update() Diagnostic(RudeEditKind.TypeUpdate, "byte a", FeaturesResources.parameter)); } + [Fact] + public void Delegates_ParameterOptionalParameter_Update() + { + var src1 = "public delegate int D(int a = 1);"; + var src2 = "public delegate int D(int a = 2);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [int a = 1]@22 -> [int a = 2]@22"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.InitializerUpdate, "int a = 2", FeaturesResources.parameter)); + } + [Fact] public void Delegates_Parameter_UpdateModifier() { @@ -1934,7 +2367,7 @@ abstract class D { public extern D(); - public static extern int P { [DllImport(""msvcrt.dll"")]get; } + public static extern int P { [DllImport(""msvcrt.dll"")]get; [DllImport(""msvcrt.dll"")]set; } [DllImport(""msvcrt.dll"")] public static extern int puts(string c); @@ -1950,7 +2383,7 @@ abstract class D var edits = GetTopEdits(src1, src2); // Adding P/Invoke is not supported by the CLR. - edits.VerifyRudeDiagnostics( + edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.InsertExtern, "public extern D()", FeaturesResources.constructor), Diagnostic(RudeEditKind.InsertExtern, "public static extern int P", FeaturesResources.property_), Diagnostic(RudeEditKind.InsertExtern, "public static extern int puts(string c)", FeaturesResources.method), @@ -2023,8 +2456,8 @@ public void NestedClass_MethodDeleteInsert() "Delete [public void goo() {}]@17", "Delete [()]@32"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "public class C", FeaturesResources.method)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "public class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))); } [Fact] @@ -2043,152 +2476,1086 @@ public void NestedClass_ClassDeleteInsert() Diagnostic(RudeEditKind.Move, "public class X", FeaturesResources.class_)); } - #endregion - - #region Namespaces + /// + /// A new generic type can be added whether it's nested and inherits generic parameters from the containing type, or top-level. + /// + [Fact] + public void NestedClassGeneric_Insert() + { + var src1 = @" +using System; +class C +{ +} +"; + var src2 = @" +using System; +class C +{ + class D {} + struct S {} + enum N {} + interface I {} + delegate void D(); +} + +class D +{ + +} +"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void NestedEnum_InsertMember() + { + var src1 = "struct S { enum N { A = 1 } }"; + var src2 = "struct S { enum N { A = 1, B = 2 } }"; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits( + "Update [enum N { A = 1 }]@11 -> [enum N { A = 1, B = 2 }]@11", + "Insert [B = 2]@27"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "B = 2", FeaturesResources.enum_value)); + } + + [Fact, WorkItem(50876, "https://github.com/dotnet/roslyn/issues/50876")] + public void NestedEnumInPartialType_InsertDelete() + { + var srcA1 = "partial struct S { }"; + var srcB1 = "partial struct S { enum N { A = 1 } }"; + var srcA2 = "partial struct S { enum N { A = 1 } }"; + var srcB2 = "partial struct S { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults() + }); + } + + [Fact, WorkItem(50876, "https://github.com/dotnet/roslyn/issues/50876")] + public void NestedEnumInPartialType_InsertDeleteAndUpdateMember() + { + var srcA1 = "partial struct S { }"; + var srcB1 = "partial struct S { enum N { A = 1 } }"; + var srcA2 = "partial struct S { enum N { A = 2 } }"; + var srcB2 = "partial struct S { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + diagnostics: new[] + { + Diagnostic(RudeEditKind.InitializerUpdate, "A = 2", FeaturesResources.enum_value), + }), + + DocumentResults() + }); + } + + [Fact, WorkItem(50876, "https://github.com/dotnet/roslyn/issues/50876")] + public void NestedEnumInPartialType_InsertDeleteAndUpdateBase() + { + var srcA1 = "partial struct S { }"; + var srcB1 = "partial struct S { enum N : uint { A = 1 } }"; + var srcA2 = "partial struct S { enum N : int { A = 1 } }"; + var srcB2 = "partial struct S { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + diagnostics: new[] + { + Diagnostic(RudeEditKind.EnumUnderlyingTypeUpdate, "enum N", FeaturesResources.enum_), + }), + + DocumentResults() + }); + } + + [Fact, WorkItem(50876, "https://github.com/dotnet/roslyn/issues/50876")] + public void NestedEnumInPartialType_InsertDeleteAndInsertMember() + { + var srcA1 = "partial struct S { }"; + var srcB1 = "partial struct S { enum N { A = 1 } }"; + var srcA2 = "partial struct S { enum N { A = 1, B = 2 } }"; + var srcB2 = "partial struct S { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.Insert, "B = 2", FeaturesResources.enum_value) }), + + DocumentResults() + }); + } + + [Fact] + public void NestedDelegateInPartialType_InsertDelete() + { + var srcA1 = "partial struct S { }"; + var srcB1 = "partial struct S { delegate void D(); }"; + var srcA2 = "partial struct S { delegate void D(); }"; + var srcB2 = "partial struct S { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + // delegate does not have any user-defined method body and this does not need a PDB update + semanticEdits: NoSemanticEdits), + + DocumentResults() + }); + } + + [Fact] + public void NestedDelegateInPartialType_InsertDeleteAndChangeSignature() + { + var srcA1 = "partial struct S { }"; + var srcB1 = "partial struct S { delegate void D(); }"; + var srcA2 = "partial struct S { delegate void D(int x); }"; + var srcB2 = "partial struct S { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + diagnostics: new[] + { + Diagnostic(RudeEditKind.Insert, "int x", FeaturesResources.parameter) + }), + + DocumentResults() + }); + } + + [Fact] + public void NestedDelegateInPartialType_InsertDeleteAndChangeOptionalParameterValue() + { + var srcA1 = "partial struct S { }"; + var srcB1 = "partial struct S { delegate void D(int x = 1); }"; + var srcA2 = "partial struct S { delegate void D(int x = 2); }"; + var srcB2 = "partial struct S { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + diagnostics: new[] + { + Diagnostic(RudeEditKind.InitializerUpdate, "int x = 2", FeaturesResources.parameter) + }), + + DocumentResults() + }); + } + + [Fact] + public void NestedPartialTypeInPartialType_InsertDeleteAndChange() + { + var srcA1 = "partial struct S { partial class C { void F1() {} } }"; + var srcB1 = "partial struct S { partial class C { void F2(byte x) {} } }"; + var srcC1 = "partial struct S { }"; + + var srcA2 = "partial struct S { partial class C { void F1() {} } }"; + var srcB2 = "partial struct S { }"; + var srcC2 = "partial struct S { partial class C { void F2(int x) {} } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2) }, + new[] + { + DocumentResults(), + + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.Delete, "partial struct S", DeletedSymbolDisplay(FeaturesResources.method, "F2(byte)")) }), + + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("S").GetMember("C").GetMember("F2")) }) + }); + } + + [Fact] + public void NestedPartialTypeInPartialType_InsertDeleteAndChange_BaseType() + { + var srcA1 = "partial class C { }"; + var srcB1 = ""; + var srcC1 = "partial class C { }"; + + var srcA2 = ""; + var srcB2 = "partial class C : D { }"; + var srcC2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2) }, + new[] + { + DocumentResults(), + + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "partial class C", FeaturesResources.class_) }), + + DocumentResults(), + }); + } + + [Fact] + public void NestedPartialTypeInPartialType_InsertDeleteAndChange_Attribute() + { + var srcA1 = "partial class C { }"; + var srcB1 = ""; + var srcC1 = "partial class C { }"; + + var srcA2 = ""; + var srcB2 = "[A]partial class C { }"; + var srcC2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2) }, + new[] + { + DocumentResults(), + + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.Update, "partial class C", FeaturesResources.class_) }), + + DocumentResults(), + }); + } + + [Fact] + public void NestedPartialTypeInPartialType_InsertDeleteAndChange_TypeParameter() + { + var srcA1 = "partial class C { }"; + var srcB1 = ""; + var srcC1 = "partial class C { }"; + + var srcA2 = ""; + var srcB2 = "partial class C<[A]T> { }"; + var srcC2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2) }, + new[] + { + DocumentResults(), + + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.Update, "partial class C<[A]T>", FeaturesResources.class_) }), + + DocumentResults(), + }); + } + + [Fact] + public void NestedPartialTypeInPartialType_InsertDeleteAndChange_Constraint() + { + var srcA1 = "partial class C { }"; + var srcB1 = ""; + var srcC1 = "partial class C { }"; + + var srcA2 = ""; + var srcB2 = "partial class C where T : new() { }"; + var srcC2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2) }, + new[] + { + DocumentResults(), + + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.Update, "partial class C", FeaturesResources.class_) }), + + DocumentResults(), + }); + } + + /// + /// Moves partial classes to different files while moving around their attributes and base interfaces. + /// + [Fact] + public void NestedPartialTypeInPartialType_InsertDeleteRefactor() + { + var srcA1 = "partial class C : I { void F() { } }"; + var srcB1 = "[A][B]partial class C : J { void G() { } }"; + var srcC1 = ""; + var srcD1 = ""; + + var srcA2 = ""; + var srcB2 = ""; + var srcC2 = "[A]partial class C : I, J { void F() { } }"; + var srcD2 = "[B]partial class C { void G() { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2), GetTopEdits(srcD1, srcD2) }, + new[] + { + DocumentResults(), + DocumentResults(), + + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")) }), + + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("G")) }), + }); + } + + /// + /// Moves partial classes to different files while moving around their attributes and base interfaces. + /// Currently we do not support splitting attribute lists. + /// + [Fact] + public void NestedPartialTypeInPartialType_InsertDeleteRefactor_AttributeListSplitting() + { + var srcA1 = "partial class C { void F() { } }"; + var srcB1 = "[A,B]partial class C { void G() { } }"; + var srcC1 = ""; + var srcD1 = ""; + + var srcA2 = ""; + var srcB2 = ""; + var srcC2 = "[A]partial class C { void F() { } }"; + var srcD2 = "[B]partial class C { void G() { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2), GetTopEdits(srcD1, srcD2) }, + new[] + { + DocumentResults(), + DocumentResults(), + DocumentResults(diagnostics: new[] { Diagnostic(RudeEditKind.Update, "partial class C", FeaturesResources.class_) }), + DocumentResults(diagnostics: new[] { Diagnostic(RudeEditKind.Update, "partial class C", FeaturesResources.class_) }), + }); + } + + [Fact] + public void NestedPartialTypeInPartialType_InsertDeleteChangeMember() + { + var srcA1 = "partial class C { void F(int y = 1) { } }"; + var srcB1 = "partial class C { void G(int x = 1) { } }"; + var srcC1 = ""; + + var srcA2 = ""; + var srcB2 = "partial class C { void G(int x = 2) { } }"; + var srcC2 = "partial class C { void F(int y = 2) { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2) }, + new[] + { + DocumentResults(), + DocumentResults(diagnostics: new[] { Diagnostic(RudeEditKind.InitializerUpdate, "int x = 2", FeaturesResources.parameter) }), + DocumentResults(diagnostics: new[] { Diagnostic(RudeEditKind.InitializerUpdate, "int y = 2", FeaturesResources.parameter) }), + }); + } + + [Fact] + public void NestedPartialTypeInPartialType_InsertDeleteAndInsertVirtual() + { + var srcA1 = "partial interface I { partial class C { virtual void F1() {} } }"; + var srcB1 = "partial interface I { partial class C { virtual void F2() {} } }"; + var srcC1 = "partial interface I { partial class C { } }"; + var srcD1 = "partial interface I { partial class C { } }"; + var srcE1 = "partial interface I { }"; + var srcF1 = "partial interface I { }"; + + var srcA2 = "partial interface I { partial class C { } }"; + var srcB2 = ""; + var srcC2 = "partial interface I { partial class C { virtual void F1() {} } }"; // move existing virtual into existing partial decl + var srcD2 = "partial interface I { partial class C { virtual void N1() {} } }"; // insert new virtual into existing partial decl + var srcE2 = "partial interface I { partial class C { virtual void F2() {} } }"; // move existing virtual into a new partial decl + var srcF2 = "partial interface I { partial class C { virtual void N2() {} } }"; // insert new virtual into new partial decl + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2), GetTopEdits(srcD1, srcD2), GetTopEdits(srcE1, srcE2), GetTopEdits(srcF1, srcF2) }, + new[] + { + // A + DocumentResults(), + + // B + DocumentResults(), + + // C + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("C").GetMember("F1")) }), + + // D + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.InsertVirtual, "virtual void N1()", FeaturesResources.method) }), + + // E + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("C").GetMember("F2")) }), + + // F + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.InsertVirtual, "virtual void N2()", FeaturesResources.method) }), + }); + } + + #endregion + + #region Namespaces + + [Fact] + public void Namespace_Insert() + { + var src1 = @""; + var src2 = @"namespace C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert [namespace C { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "namespace C", FeaturesResources.namespace_)); + + } + [Fact] + public void Namespace_InsertNested() + { + var src1 = @"namespace C { }"; + var src2 = @"namespace C { namespace D { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert [namespace D { }]@14"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "namespace D", FeaturesResources.namespace_)); + } + + [Fact] + public void NamespaceDelete() + { + var src1 = @"namespace C { namespace D { } }"; + var src2 = @"namespace C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Delete [namespace D { }]@14"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "namespace C", FeaturesResources.namespace_)); + } + + [Fact] + public void NamespaceMove1() + { + var src1 = @"namespace C { namespace D { } }"; + var src2 = @"namespace C { } namespace D { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Move [namespace D { }]@14 -> @16"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Move, "namespace D", FeaturesResources.namespace_)); + } + + [Fact] + public void NamespaceReorder1() + { + var src1 = @"namespace C { namespace D { } class T { } namespace E { } }"; + var src2 = @"namespace C { namespace E { } class T { } namespace D { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Reorder [class T { }]@30 -> @30", + "Reorder [namespace E { }]@42 -> @14"); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void NamespaceReorder2() + { + var src1 = @"namespace C { namespace D1 { } namespace D2 { } namespace D3 { } class T { } namespace E { } }"; + var src2 = @"namespace C { namespace E { } class T { } namespace D1 { } namespace D2 { } namespace D3 { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Reorder [class T { }]@65 -> @65", + "Reorder [namespace E { }]@77 -> @14"); + + edits.VerifyRudeDiagnostics(); + } + + #endregion + + #region Members + + [Fact] + public void MemberUpdate_Modifier_ReadOnly_Remove() + { + var src1 = @" +using System; + +struct S +{ + // methods + public readonly int M() => 1; + + // properties + public readonly int P => 1; + public readonly int Q { get; } + public int R { readonly get; readonly set; } + + // events + public readonly event Action E { add {} remove {} } + public event Action F { readonly add {} readonly remove {} } +}"; + var src2 = @" +using System; +struct S +{ + // methods + public int M() => 1; + + // properties + public int P => 1; + public int Q { get; } + public int R { get; set; } + + // events + public event Action E { add {} remove {} } + public event Action F { add {} remove {} } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public int M()", FeaturesResources.method), + Diagnostic(RudeEditKind.ModifiersUpdate, "public int P", FeaturesResources.property_), + Diagnostic(RudeEditKind.ModifiersUpdate, "public int Q", FeaturesResources.auto_property), + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.property_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "set", CSharpFeaturesResources.property_setter), + Diagnostic(RudeEditKind.ModifiersUpdate, "add", FeaturesResources.event_accessor), + Diagnostic(RudeEditKind.ModifiersUpdate, "remove", FeaturesResources.event_accessor)); + } + + [Fact] + public void MemberUpdate_Modifier_ReadOnly_Add() + { + var src1 = @" +using System; + +struct S +{ + // methods + public int M() => 1; + + // properties + public int P => 1; + public int Q { get; } + public int R { get; set; } + + // events + public event Action E { add {} remove {} } + public event Action F { add {} remove {} } +}"; + var src2 = @" +using System; + +struct S +{ + // methods + public readonly int M() => 1; + + // properties + public readonly int P => 1; + public readonly int Q { get; } + public int R { readonly get; readonly set; } + + // events + public readonly event Action E { add {} remove {} } + public event Action F { readonly add {} readonly remove {} } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int M()", FeaturesResources.method), + Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int P", FeaturesResources.property_), + Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int Q", FeaturesResources.auto_property), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly get", CSharpFeaturesResources.property_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly set", CSharpFeaturesResources.property_setter), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly add", FeaturesResources.event_accessor), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly remove", FeaturesResources.event_accessor)); + } + + [Fact] + public void PartialMember_DeleteInsert_SingleDocument() + { + var src1 = @" +using System; + +partial class C +{ + void M() {} + int P1 { get; set; } + int P2 { get => 1; set {} } + int this[int i] { get => 1; set {} } + int this[byte i] { get => 1; set {} } + event Action E { add {} remove {} } + event Action EF; + int F1; + int F2; +} + +partial class C +{ +} +"; + var src2 = @" +using System; + +partial class C +{ +} + +partial class C +{ + void M() {} + int P1 { get; set; } + int P2 { get => 1; set {} } + int this[int i] { get => 1; set {} } + int this[byte i] { get => 1; set {} } + event Action E { add {} remove {} } + event Action EF; + int F1, F2; +} +"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert [void M() {}]@68", + "Insert [int P1 { get; set; }]@85", + "Insert [int P2 { get => 1; set {} }]@111", + "Insert [int this[int i] { get => 1; set {} }]@144", + "Insert [int this[byte i] { get => 1; set {} }]@186", + "Insert [event Action E { add {} remove {} }]@229", + "Insert [event Action EF;]@270", + "Insert [int F1, F2;]@292", + "Insert [()]@74", + "Insert [{ get; set; }]@92", + "Insert [{ get => 1; set {} }]@118", + "Insert [[int i]]@152", + "Insert [{ get => 1; set {} }]@160", + "Insert [[byte i]]@194", + "Insert [{ get => 1; set {} }]@203", + "Insert [{ add {} remove {} }]@244", + "Insert [Action EF]@276", + "Insert [int F1, F2]@292", + "Insert [get;]@94", + "Insert [set;]@99", + "Insert [get => 1;]@120", + "Insert [set {}]@130", + "Insert [int i]@153", + "Insert [get => 1;]@162", + "Insert [set {}]@172", + "Insert [byte i]@195", + "Insert [get => 1;]@205", + "Insert [set {}]@215", + "Insert [add {}]@246", + "Insert [remove {}]@253", + "Insert [EF]@283", + "Insert [F1]@296", + "Insert [F2]@300", + "Delete [void M() {}]@43", + "Delete [()]@49", + "Delete [int P1 { get; set; }]@60", + "Delete [{ get; set; }]@67", + "Delete [get;]@69", + "Delete [set;]@74", + "Delete [int P2 { get => 1; set {} }]@86", + "Delete [{ get => 1; set {} }]@93", + "Delete [get => 1;]@95", + "Delete [set {}]@105", + "Delete [int this[int i] { get => 1; set {} }]@119", + "Delete [[int i]]@127", + "Delete [int i]@128", + "Delete [{ get => 1; set {} }]@135", + "Delete [get => 1;]@137", + "Delete [set {}]@147", + "Delete [int this[byte i] { get => 1; set {} }]@161", + "Delete [[byte i]]@169", + "Delete [byte i]@170", + "Delete [{ get => 1; set {} }]@178", + "Delete [get => 1;]@180", + "Delete [set {}]@190", + "Delete [event Action E { add {} remove {} }]@204", + "Delete [{ add {} remove {} }]@219", + "Delete [add {}]@221", + "Delete [remove {}]@228", + "Delete [event Action EF;]@245", + "Delete [Action EF]@251", + "Delete [EF]@258", + "Delete [int F1;]@267", + "Delete [int F1]@267", + "Delete [F1]@271", + "Delete [int F2;]@280", + "Delete [int F2]@280", + "Delete [F2]@284"); + + EditAndContinueValidation.VerifySemantics( + new[] { edits }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("M"), preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P2").GetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P2").SetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("this[]").Cast().Single(m => m.GetParameters().Single().Type.Name == "Int32").GetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("this[]").Cast().Single(m => m.GetParameters().Single().Type.Name == "Int32").SetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("this[]").Cast().Single(m => m.GetParameters().Single().Type.Name == "Byte").GetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("this[]").Cast().Single(m => m.GetParameters().Single().Type.Name == "Byte").SetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("E").AddMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("E").RemoveMethod, preserveLocalVariables: false), + }) + }); + } + + [Fact] + public void PartialMember_InsertDelete_MultipleDocuments() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { void F() {} }"; + var srcA2 = "partial class C { void F() {} }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F"), preserveLocalVariables: false) + }), + + DocumentResults() + }); + } + + [Fact] + public void PartialMember_DeleteInsert_MultipleDocuments() + { + var srcA1 = "partial class C { void F() {} }"; + var srcB1 = "partial class C { }"; + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { void F() {} }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F"), preserveLocalVariables: false) + }) + }); + } + + [Fact] + public void PartialMember_DeleteInsert_GenericMethod() + { + var srcA1 = "partial class C { void F() {} }"; + var srcB1 = "partial class C { }"; + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { void F() {} }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(diagnostics: new[] + { + // TODO: better message + Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "void F()", FeaturesResources.method) + }) + }); + } + + [Fact] + public void PartialMember_DeleteInsert_GenericType() + { + var srcA1 = "partial class C { void F() {} }"; + var srcB1 = "partial class C { }"; + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { void F() {} }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(diagnostics: new[] + { + // TODO: better message + Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method) + }) + }); + } + + [Fact] + public void PartialMember_DeleteInsert_Destructor() + { + var srcA1 = "partial class C { ~C() {} }"; + var srcB1 = "partial class C { }"; + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { ~C() {} }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("Finalize"), preserveLocalVariables: false), + }) + }); + } + + [Fact] + public void PartialNestedType_InsertDeleteAndChange() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { class D { void M() {} } interface I { } }"; + + var srcA2 = "partial class C { class D : I { void M() {} } interface I { } }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + diagnostics: new[] + { + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "class D", FeaturesResources.class_), + }), + + DocumentResults() + }); + } - [Fact] - public void NamespaceMove1() + [Fact, WorkItem(51011, "https://github.com/dotnet/roslyn/issues/51011")] + public void PartialMember_RenameInsertDelete() { - var src1 = @"namespace C { namespace D { } }"; - var src2 = @"namespace C { } namespace D { }"; + // The syntactic analysis for A and B produce rename edits since it doesn't see that the member was in fact moved. + // TODO: Currently, we don't even pass rename edits to semantic analysis where we could handle them as updates. - var edits = GetTopEdits(src1, src2); + var srcA1 = "partial class C { void F1() {} }"; + var srcB1 = "partial class C { void F2() {} }"; + var srcA2 = "partial class C { void F2() {} }"; + var srcB2 = "partial class C { void F1() {} }"; - edits.VerifyEdits( - "Move [namespace D { }]@14 -> @16"); + // current outcome: + GetTopEdits(srcA1, srcA2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Renamed, "void F2()", FeaturesResources.method)); + GetTopEdits(srcB1, srcB2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Renamed, "void F1()", FeaturesResources.method)); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "namespace D", FeaturesResources.namespace_)); + // correct outcome: + //EditAndContinueValidation.VerifySemantics( + // new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + // new[] + // { + // DocumentResults(semanticEdits: new[] + // { + // SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F2")), + // }), + + // DocumentResults( + // semanticEdits: new[] + // { + // SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F1")), + // }) + // }); } [Fact] - public void NamespaceReorder1() + public void PartialMember_DeleteInsert_UpdateMethodBodyError() { - var src1 = @"namespace C { namespace D { } class T { } namespace E { } }"; - var src2 = @"namespace C { namespace E { } class T { } namespace D { } }"; + var srcA1 = @" +using System.Collections.Generic; - var edits = GetTopEdits(src1, src2); +partial class C +{ + IEnumerable F() { yield return 1; } +} +"; + var srcB1 = @" +using System.Collections.Generic; - edits.VerifyEdits( - "Reorder [class T { }]@30 -> @30", - "Reorder [namespace E { }]@42 -> @14"); +partial class C +{ +} +"; - edits.VerifyRudeDiagnostics(); + var srcA2 = @" +using System.Collections.Generic; + +partial class C +{ +} +"; + var srcB2 = @" +using System.Collections.Generic; + +partial class C +{ + IEnumerable F() { yield return 1; yield return 2; } +} +"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(diagnostics: new[] + { + Diagnostic(RudeEditKind.Insert, "yield return 2;", CSharpFeaturesResources.yield_return_statement) + }) + }); } [Fact] - public void NamespaceReorder2() + public void PartialMember_DeleteInsert_UpdatePropertyAccessors() { - var src1 = @"namespace C { namespace D1 { } namespace D2 { } namespace D3 { } class T { } namespace E { } }"; - var src2 = @"namespace C { namespace E { } class T { } namespace D1 { } namespace D2 { } namespace D3 { } }"; - - var edits = GetTopEdits(src1, src2); + var srcA1 = "partial class C { int P { get => 1; set { Console.WriteLine(1); } } }"; + var srcB1 = "partial class C { }"; - edits.VerifyEdits( - "Reorder [class T { }]@65 -> @65", - "Reorder [namespace E { }]@77 -> @14"); + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int P { get => 2; set { Console.WriteLine(2); } } }"; - edits.VerifyRudeDiagnostics(); + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").SetMethod) + }) + }); } - #endregion - - #region Members - [Fact] - public void MemberUpdate_Modifier_ReadOnly_Remove() + public void PartialMember_DeleteInsert_UpdateAutoProperty() { - var src1 = @" -using System; + var srcA1 = "partial class C { int P => 1; }"; + var srcB1 = "partial class C { }"; -struct S -{ - // methods - public readonly int M() => 1; + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int P => 2; }"; - // properties - public readonly int P => 1; - public readonly int Q { get; } - public int R { readonly get; readonly set; } + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").GetMethod) + }) + }); + } - // events - public readonly event Action E { add {} remove {} } - public event Action F { readonly add {} readonly remove {} } -}"; - var src2 = @" -using System; -struct S -{ - // methods - public int M() => 1; + [Fact] + public void PartialMember_DeleteInsert_AddFieldInitializer() + { + var srcA1 = "partial class C { int f; }"; + var srcB1 = "partial class C { }"; - // properties - public int P => 1; - public int Q { get; } - public int R { get; set; } + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int f = 1; }"; - // events - public event Action E { add {} remove {} } - public event Action F { add {} remove {} } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public int M()", FeaturesResources.method), - Diagnostic(RudeEditKind.ModifiersUpdate, "public int P", FeaturesResources.property_), - Diagnostic(RudeEditKind.ModifiersUpdate, "public int Q", FeaturesResources.auto_property), - Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.property_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "set", CSharpFeaturesResources.property_setter), - Diagnostic(RudeEditKind.ModifiersUpdate, "add", FeaturesResources.event_accessor), - Diagnostic(RudeEditKind.ModifiersUpdate, "remove", FeaturesResources.event_accessor)); + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }) + }); } [Fact] - public void MemberUpdate_Modifier_ReadOnly_Add() + public void PartialMember_DeleteInsert_RemoveFieldInitializer() { - var src1 = @" -using System; - -struct S -{ - // methods - public int M() => 1; + var srcA1 = "partial class C { int f = 1; }"; + var srcB1 = "partial class C { }"; - // properties - public int P => 1; - public int Q { get; } - public int R { get; set; } + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int f; }"; - // events - public event Action E { add {} remove {} } - public event Action F { add {} remove {} } -}"; - var src2 = @" -using System; + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }) + }); + } -struct S -{ - // methods - public readonly int M() => 1; + [Fact] + public void PartialMember_DeleteInsert_ConstructorWithInitializers() + { + var srcA1 = "partial class C { int f = 1; C(int x) { f = x; } }"; + var srcB1 = "partial class C { }"; - // properties - public readonly int P => 1; - public readonly int Q { get; } - public int R { readonly get; readonly set; } + var srcA2 = "partial class C { int f = 1; }"; + var srcB2 = "partial class C { C(int x) { f = x + 1; } }"; - // events - public readonly event Action E { add {} remove {} } - public event Action F { readonly add {} readonly remove {} } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int M()", FeaturesResources.method), - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int P", FeaturesResources.property_), - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int Q", FeaturesResources.auto_property), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly get", CSharpFeaturesResources.property_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly set", CSharpFeaturesResources.property_setter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly add", FeaturesResources.event_accessor), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly remove", FeaturesResources.event_accessor)); + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }) + }); } #endregion @@ -2380,20 +3747,11 @@ public void Method_Delete() class C { void goo() { } - - static void Main(string[] args) - { - Console.ReadLine(); - } } "; var src2 = @" class C { - static void Main(string[] args) - { - Console.ReadLine(); - } }"; var edits = GetTopEdits(src1, src2); @@ -2401,8 +3759,8 @@ static void Main(string[] args) "Delete [void goo() { }]@18", "Delete [()]@26"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.method)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))); } [Fact] @@ -2412,20 +3770,11 @@ public void MethodWithExpressionBody_Delete() class C { int goo() => 1; - - static void Main(string[] args) - { - Console.ReadLine(); - } } "; var src2 = @" class C { - static void Main(string[] args) - { - Console.ReadLine(); - } }"; var edits = GetTopEdits(src1, src2); @@ -2433,66 +3782,24 @@ static void Main(string[] args) "Delete [int goo() => 1;]@18", "Delete [()]@25"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.method)); - } - - [Fact] - public void MethodDelete_WithParameters() - { - var src1 = @" -class C -{ - void goo(int a) { } - - static void Main(string[] args) - { - Console.ReadLine(); - } -} -"; - var src2 = @" -class C -{ - static void Main(string[] args) - { - Console.ReadLine(); - } -}"; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Delete [void goo(int a) { }]@18", - "Delete [(int a)]@26", - "Delete [int a]@27"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.method)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))); } [WorkItem(754853, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/754853")] [Fact] - public void MethodDelete_WithAttribute() + public void MethodDelete_WithParameterAndAttribute() { var src1 = @" class C { [Obsolete] void goo(int a) { } - - static void Main(string[] args) - { - Console.ReadLine(); - } } "; var src2 = @" class C { - static void Main(string[] args) - { - Console.ReadLine(); - } }"; var edits = GetTopEdits(src1, src2); @@ -2504,8 +3811,8 @@ void goo(int a) { }]@18", "Delete [(int a)]@42", "Delete [int a]@43"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.method)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "goo(int)"))); } [WorkItem(754853, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/754853")] @@ -2520,11 +3827,6 @@ class C { [DllImport(""msvcrt.dll"")] public static extern int puts(string c); - - static void Main(string[] args) - { - Console.ReadLine(); - } } "; var src2 = @" @@ -2533,10 +3835,6 @@ static void Main(string[] args) class C { - static void Main(string[] args) - { - Console.ReadLine(); - } }"; var edits = GetTopEdits(src1, src2); @@ -2549,8 +3847,8 @@ static void Main(string[] args) "Delete [(string c)]@134", "Delete [string c]@135"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.method)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "puts(string)"))); } [Fact] @@ -2675,7 +3973,7 @@ public virtual void F() {} "; var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( + edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.InsertVirtual, "public virtual void F()", FeaturesResources.method)); } @@ -2694,7 +3992,7 @@ abstract class C "; var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( + edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.InsertVirtual, "public abstract void F()", FeaturesResources.method)); } @@ -2713,13 +4011,13 @@ public override void F() { } "; var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( + edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.InsertVirtual, "public override void F()", FeaturesResources.method)); } [WorkItem(755784, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/755784"), WorkItem(835827, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/835827")] [Fact] - public void PrivateMethodInsert_PInvoke1() + public void ExternMethodInsert() { var src1 = @" using System; @@ -2727,10 +4025,6 @@ public void PrivateMethodInsert_PInvoke1() class C { - static void Main(string[] args) - { - Console.ReadLine(); - } }"; var src2 = @" using System; @@ -2740,11 +4034,6 @@ class C { [DllImport(""msvcrt.dll"")] private static extern int puts(string c); - - static void Main(string[] args) - { - Console.ReadLine(); - } } "; var edits = GetTopEdits(src1, src2); @@ -2758,10 +4047,51 @@ static void Main(string[] args) "Insert [string c]@136"); // CLR doesn't support methods without a body - edits.VerifyRudeDiagnostics( + edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.InsertExtern, "private static extern int puts(string c)", FeaturesResources.method)); } + [WorkItem(755784, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/755784"), WorkItem(835827, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/835827")] + [Fact] + public void ExternMethodDeleteInsert() + { + var srcA1 = @" +using System; +using System.Runtime.InteropServices; + +class C +{ + [DllImport(""msvcrt.dll"")] + private static extern int puts(string c); +}"; + var srcA2 = @" +using System; +using System.Runtime.InteropServices; +"; + + var srcB1 = @" +using System; +using System.Runtime.InteropServices; +"; + var srcB2 = @" +using System; +using System.Runtime.InteropServices; + +class C +{ + [DllImport(""msvcrt.dll"")] + private static extern int puts(string c); +} +"; + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults() + }); + } + [Fact] public void MethodReorder1() { @@ -3983,10 +5313,162 @@ public class SubClass : BaseClass, IConflict "Insert [string IConflict.Get() => String.Empty;]@325", "Insert [()]@345"); - edits.VerifyRudeDiagnostics( + edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.InsertMethodWithExplicitInterfaceSpecifier, "string IConflict.Get()", FeaturesResources.method)); } + [Fact] + public void PartialMethod_DeleteInsert_DefinitionPart() + { + var srcA1 = "partial class C { partial void F(); }"; + var srcB1 = "partial class C { partial void F() { } }"; + var srcC1 = "partial class C { }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { partial void F() { } }"; + var srcC2 = "partial class C { partial void F(); }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2) }, + new[] + { + DocumentResults(), + DocumentResults(), + DocumentResults(), + }); + } + + [Fact] + public void PartialMethod_DeleteInsert_ImplementationPart() + { + var srcA1 = "partial class C { partial void F(); }"; + var srcB1 = "partial class C { partial void F() { } }"; + var srcC1 = "partial class C { }"; + + var srcA2 = "partial class C { partial void F(); }"; + var srcB2 = "partial class C { }"; + var srcC2 = "partial class C { partial void F() { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2) }, + new[] + { + DocumentResults(), + DocumentResults(), + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F").PartialImplementationPart) }), + }); + } + + [Fact, WorkItem(51011, "https://github.com/dotnet/roslyn/issues/51011")] + public void PartialMethod_Swap_ImplementationAndDefinitionParts() + { + var srcA1 = "partial class C { partial void F(); }"; + var srcB1 = "partial class C { partial void F() { } }"; + + var srcA2 = "partial class C { partial void F() { } }"; + var srcB2 = "partial class C { partial void F(); }"; + + // current: + GetTopEdits(srcA1, srcA2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.MethodBodyAdd, "partial void F()", FeaturesResources.method)); + GetTopEdits(srcB1, srcB2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.MethodBodyDelete, "partial void F()", FeaturesResources.method)); + + // correct: TODO + //EditAndContinueValidation.VerifySemantics( + // new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + // new[] + // { + // DocumentResults(), + // DocumentResults( + // semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")) }), + // }); + } + + [Fact] + public void PartialMethod_DeleteImplementation() + { + var srcA1 = "partial class C { partial void F(); }"; + var srcB1 = "partial class C { partial void F() { } }"; + + var srcA2 = "partial class C { partial void F(); }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.Delete, "partial class C", DeletedSymbolDisplay(FeaturesResources.method, "F()")) }) + }); + } + + [Fact] + public void PartialMethod_DeleteBoth() + { + var srcA1 = "partial class C { partial void F(); }"; + var srcB1 = "partial class C { partial void F() { } }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.Delete, "partial class C", DeletedSymbolDisplay(FeaturesResources.method, "F()")) }) + }); + } + + [Fact] + public void PartialMethod_DeleteInsertBoth() + { + var srcA1 = "partial class C { partial void F(); }"; + var srcB1 = "partial class C { partial void F() { } }"; + var srcC1 = "partial class C { }"; + var srcD1 = "partial class C { }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { }"; + var srcC2 = "partial class C { partial void F(); }"; + var srcD2 = "partial class C { partial void F() { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2), GetTopEdits(srcD1, srcD2) }, + new[] + { + DocumentResults(), + DocumentResults(), + DocumentResults(), + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F").PartialImplementationPart) }) + }); + } + + [Fact] + public void PartialMethod_Insert() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { }"; + + var srcA2 = "partial class C { partial void F(); }"; + var srcB2 = "partial class C { partial void F() { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").GetMember("F").PartialImplementationPart) }), + }); + } + #endregion #region Operators @@ -4014,7 +5496,7 @@ public static implicit operator bool (C c) }"; var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( + edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.InsertOperator, "public static implicit operator bool (C c)", CSharpFeaturesResources.conversion_operator), Diagnostic(RudeEditKind.InsertOperator, "public static C operator +(C c, C d)", FeaturesResources.operator_)); } @@ -4042,9 +5524,46 @@ class C }"; var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", CSharpFeaturesResources.conversion_operator), - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.operator_)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(CSharpFeaturesResources.conversion_operator, "implicit operator bool(C)")), + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.operator_, "operator +(C, C)"))); + } + + [Fact] + public void OperatorInsertDelete() + { + var srcA1 = @" +partial class C +{ + public static implicit operator bool (C c) => false; +} +"; + var srcB1 = @" +partial class C +{ + public static C operator +(C c, C d) => c; +} +"; + + var srcA2 = srcB1; + var srcB2 = srcA1; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("op_Addition")) + }), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("op_Implicit")) + }), + }); } [Fact] @@ -4204,7 +5723,7 @@ public void Operator_ReadOnlyRef_Parameter_InsertWhole() "Insert [(in Test b)]@42", "Insert [in Test b]@43"); - edits.VerifyRudeDiagnostics( + edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.InsertOperator, "public static bool operator !(in Test b)", FeaturesResources.operator_)); } @@ -4321,21 +5840,11 @@ public void ConstructorUpdate_AddParameter() class C { public C(int a) { } - - static void Main(string[] args) - { - C c = new C(5); - } }"; var src2 = @" class C { public C(int a, int b) { } - - static void Main(string[] args) - { - C c = new C(5); - } }"; var edits = GetTopEdits(src1, src2); @@ -4350,108 +5859,35 @@ static void Main(string[] args) [Fact] public void DestructorDelete() { - var src1 = @" -class C -{ - static void Main(string[] args) - { - B b = new B(); - b = null; - GC.Collect(); - GC.WaitForPendingFinalizers(); - } -} -class B -{ - ~B() - { - Console.WriteLine(""B's destructor""); - } -}"; - var src2 = @" -class C -{ - static void Main(string[] args) - { - B b = new B(); - b = null; - GC.Collect(); - GC.WaitForPendingFinalizers(); - } -} -class B -{ + var src1 = @"class B { ~B() { } }"; + var src2 = @"class B { }"; -}"; - - var expectedEdit1 = @"Delete [~B() - { - Console.WriteLine(""B's destructor""); - }]@190"; + var expectedEdit1 = @"Delete [~B() { }]@10"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits(expectedEdit1); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "class B", CSharpFeaturesResources.destructor)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class B", DeletedSymbolDisplay(CSharpFeaturesResources.destructor, "~B()"))); } [Fact] public void DestructorDelete_InsertConstructor() { - var src1 = @" -class C -{ - static void Main(string[] args) - { - B b = new B(); - b = null; - GC.Collect(); - GC.WaitForPendingFinalizers(); - } -} -class B -{ - ~B() - { - Console.WriteLine(""B's destructor""); - } -}"; - var src2 = @" -class C -{ - static void Main(string[] args) - { - B b = new B(); - b = null; - GC.Collect(); - GC.WaitForPendingFinalizers(); - } -} -class B -{ - B() - { - Console.WriteLine(""B's destructor""); - } -}"; - var expectedEdit1 = @"Insert [B() - { - Console.WriteLine(""B's destructor""); - }]@190"; - - var expectedEdit2 = @"Delete [~B() - { - Console.WriteLine(""B's destructor""); - }]@190"; + var src1 = @"class B { ~B() { } }"; + var src2 = @"class B { B() { } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits(expectedEdit1, "Insert [()]@191", expectedEdit2); + edits.VerifyEdits( + "Insert [B() { }]@10", + "Insert [()]@11", + "Delete [~B() { }]@10"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "class B", CSharpFeaturesResources.destructor)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ChangingVisibility, "B()", FeaturesResources.constructor), + Diagnostic(RudeEditKind.Delete, "class B", DeletedSymbolDisplay(CSharpFeaturesResources.destructor, "~B()"))); } [Fact] @@ -4463,92 +5899,71 @@ public void ConstructorUpdate_AnonymousTypeInFieldInitializer() var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics(); - } - - [Fact] - public void StaticCtorDelete() - { - var src1 = "class C { static C() { } }"; - var src2 = "class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.constructor)); - } - - [Fact] - public void InstanceCtorDelete_Public() - { - var src1 = "class C { public C() { } }"; - var src2 = "class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) }); - } - - [Fact] - public void InstanceCtorDelete_Private1() - { - var src1 = "class C { C() { } }"; - var src2 = "class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.constructor)); + edits.VerifyRudeDiagnostics(); } [Fact] - public void InstanceCtorDelete_Private2() + public void StaticCtorDelete() { - var src1 = "class C { private C() { } }"; + var src1 = "class C { static C() { } }"; var src2 = "class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.static_constructor, "C()"))); } [Fact] - public void InstanceCtorDelete_Protected() + public void InstanceCtorDelete_Public() { - var src1 = "class C { protected C() { } }"; + var src1 = "class C { public C() { } }"; var src2 = "class C { }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.constructor)); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } - [Fact] - public void InstanceCtorDelete_Internal() - { - var src1 = "class C { internal C() { } }"; + [Theory] + [InlineData("")] + [InlineData("private")] + [InlineData("protected")] + [InlineData("internal")] + [InlineData("private protected")] + [InlineData("protected internal")] + public void InstanceCtorDelete_NonPublic(string visibility) + { + var src1 = "class C { " + visibility + " C() { } }"; var src2 = "class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] - public void InstanceCtorDelete_ProtectedInternal() + public void InstanceCtorDelete_Public_PartialWithInitializerUpdate() { - var src1 = "class C { protected internal C() { } }"; - var src2 = "class C { }"; + var srcA1 = "partial class C { public C() { } }"; + var srcB1 = "partial class C { int x = 1; }"; - var edits = GetTopEdits(src1, src2); + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int x = 2; }"; - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.constructor)); + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }), + + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }) + }); } [Fact] @@ -4588,9 +6003,13 @@ public void InstanceCtorInsert_Partial_Public_Implicit() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - expectedSemanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + // no change in document A + DocumentResults(), + + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }), }); } @@ -4621,9 +6040,16 @@ public void InstanceCtorInsert_Partial_Public_NoImplicit() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - expectedSemanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters.IsEmpty)) + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters.IsEmpty)) + }), + + // no change in document B + DocumentResults(), }); } @@ -4636,7 +6062,7 @@ public void InstanceCtorInsert_Private_Implicit1() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "private C()")); + Diagnostic(RudeEditKind.ChangingVisibility, "private C()", FeaturesResources.constructor)); } [Fact] @@ -4648,7 +6074,7 @@ public void InstanceCtorInsert_Private_Implicit2() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "C()")); + Diagnostic(RudeEditKind.ChangingVisibility, "C()", FeaturesResources.constructor)); } [Fact] @@ -4660,7 +6086,7 @@ public void InstanceCtorInsert_Protected_PublicImplicit() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "protected C()")); + Diagnostic(RudeEditKind.ChangingVisibility, "protected C()", FeaturesResources.constructor)); } [Fact] @@ -4672,7 +6098,7 @@ public void InstanceCtorInsert_Internal_PublicImplicit() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "internal C()")); + Diagnostic(RudeEditKind.ChangingVisibility, "internal C()", FeaturesResources.constructor)); } [Fact] @@ -4684,7 +6110,7 @@ public void InstanceCtorInsert_Internal_ProtectedImplicit() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "internal C()")); + Diagnostic(RudeEditKind.ChangingVisibility, "internal C()", FeaturesResources.constructor)); } [Fact] @@ -4764,11 +6190,16 @@ public void StaticCtor_Partial_DeleteInsert() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - activeStatements: ActiveStatementsDescription.Empty, - targetFrameworks: null, - expectedSemanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + }), }); } @@ -4783,10 +6214,16 @@ public void InstanceCtor_Partial_DeletePrivateInsertPrivate() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - activeStatements: ActiveStatementsDescription.Empty, - expectedSemanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }), }); } @@ -4801,10 +6238,16 @@ public void InstanceCtor_Partial_DeletePublicInsertPublic() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - activeStatements: ActiveStatementsDescription.Empty, - expectedSemanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }), }); } @@ -4819,10 +6262,14 @@ public void InstanceCtor_Partial_DeletePrivateInsertPublic() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - activeStatements: ActiveStatementsDescription.Empty, - expectedDiagnostics: new[] + new[] { - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "public C()") + // delete of the constructor in partial part will be reported as rude edit in the other document where it was inserted back with changed visibility + DocumentResults( + semanticEdits: NoSemanticEdits), + + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.ModifiersUpdate, "public C()", FeaturesResources.constructor) }), }); } @@ -4837,10 +6284,16 @@ public void StaticCtor_Partial_InsertDelete() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - activeStatements: ActiveStatementsDescription.Empty, - expectedSemanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + }), + + // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + DocumentResults(), }); } @@ -4855,10 +6308,16 @@ public void InstanceCtor_Partial_InsertPublicDeletePublic() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - activeStatements: ActiveStatementsDescription.Empty, - expectedSemanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }), + + // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + DocumentResults(), }); } @@ -4873,10 +6332,16 @@ public void InstanceCtor_Partial_InsertPrivateDeletePrivate() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - activeStatements: ActiveStatementsDescription.Empty, - expectedSemanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }), + + // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + DocumentResults(), }); } @@ -4891,9 +6356,16 @@ public void InstanceCtor_Partial_DeleteInternalInsertInternal() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - expectedSemanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }), + + // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + DocumentResults(), }); } @@ -4908,9 +6380,16 @@ public void InstanceCtor_Partial_InsertInternalDeleteInternal_WithBody() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - expectedSemanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }), + + // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + DocumentResults(), }); } @@ -4925,10 +6404,13 @@ public void InstanceCtor_Partial_InsertPublicDeletePrivate() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - activeStatements: ActiveStatementsDescription.Empty, - expectedDiagnostics: new[] + new[] { - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "public C()") + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.ModifiersUpdate, "public C()", FeaturesResources.constructor) }), + + // delete of the constructor in partial part will be reported as rude in the the other document where it was inserted with changed visibility + DocumentResults(), }); } @@ -4943,10 +6425,12 @@ public void InstanceCtor_Partial_InsertInternalDeletePrivate() EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - activeStatements: ActiveStatementsDescription.Empty, - expectedDiagnostics: new[] + new[] { - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "internal C()") + DocumentResults( + diagnostics: new[] { Diagnostic(RudeEditKind.ModifiersUpdate, "internal C()", FeaturesResources.constructor) }), + + DocumentResults(), }); } @@ -5105,7 +6589,7 @@ public C() new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), syntaxMap[0]) }); } - [Fact] + [Fact, WorkItem(2504, "https://github.com/dotnet/roslyn/issues/2504")] public void InstanceCtor_Partial_Insert_Parameterless_LambdaInInitializer1() { var src1 = @" @@ -5144,11 +6628,16 @@ partial class C } "; var edits = GetTopEdits(src1, src2); - var syntaxMap = GetSyntaxMap(src1, src2); - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), syntaxMap[0]) }); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, "public C()")); + + // TODO: + //var syntaxMap = GetSyntaxMap(src1, src2); + + //edits.VerifySemantics( + // ActiveStatementsDescription.Empty, + // new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), syntaxMap[0]) }); } [Fact, WorkItem(2504, "https://github.com/dotnet/roslyn/issues/2504")] @@ -5201,6 +6690,123 @@ partial class C // new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").Constructors.Single(), syntaxMap[0]) }); } + [Fact] + public void PartialTypes_ConstructorWithInitializerUpdates() + { + var srcA1 = @" +using System; + +partial class C +{ + C(int arg) => Console.WriteLine(0); + C(bool arg) => Console.WriteLine(1); +} +"; + var srcB1 = @" +using System; + +partial class C +{ + int a = 1; + + C(uint arg) => Console.WriteLine(2); +} +"; + + var srcA2 = @" +using System; + +partial class C +{ + C(int arg) => Console.WriteLine(0); + C(bool arg) => Console.WriteLine(1); +} +"; + var srcB2 = @" +using System; + +partial class C +{ + int a = 2; // updated field initializer + + C(uint arg) => Console.WriteLine(2); + C(byte arg) => Console.WriteLine(3); // new ctor +} +"; + var syntaxMapB = GetSyntaxMap(srcB1, srcB2)[0]; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + // No changes in document A + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Single().Type.Name == "Int32"), syntaxMap: syntaxMapB), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Single().Type.Name == "Boolean"), syntaxMap: syntaxMapB), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Single().Type.Name == "UInt32"), syntaxMap: syntaxMapB), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Single().Type.Name == "Byte"), syntaxMap: null), + }) + }); + } + + [Fact] + public void PartialTypes_ConstructorWithInitializerUpdates_SemanticErrors() + { + var srcA1 = @" +using System; + +partial class C +{ + C(int arg) => Console.WriteLine(0); + C(int arg) => Console.WriteLine(1); +} +"; + var srcB1 = @" +using System; + +partial class C +{ + int a = 1; +} +"; + + var srcA2 = @" +using System; + +partial class C +{ + C(int arg) => Console.WriteLine(0); + C(int arg) => Console.WriteLine(1); +} +"; + var srcB2 = @" +using System; + +partial class C +{ + int a = 2; + + C(int arg) => Console.WriteLine(2); +} +"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + // No changes in document A + DocumentResults(), + + // The actual edits do not matter since there are semantic errors in the compilation. + // We just should not crash. + DocumentResults(diagnostics: Array.Empty()) + }); + } + [WorkItem(2068, "https://github.com/dotnet/roslyn/issues/2068")] [Fact] public void Insert_ExternConstruct() @@ -5214,11 +6820,11 @@ public void Insert_ExternConstruct() "Insert [public extern C();]@10", "Insert [()]@25"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.InsertExtern, "public extern C()", FeaturesResources.constructor)); + // The compiler generates an empty constructor. + edits.VerifySemanticDiagnostics(); } - [Fact(Skip = "https://github.com/dotnet/roslyn/pull/18940")] + [Fact] public void ParameterlessConstructor_SemanticError_Delete1() { var src1 = @" @@ -5234,19 +6840,9 @@ class C "; var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics(); - } - - [Fact(Skip = "https://github.com/dotnet/roslyn/pull/18940")] - public void ParameterlessConstructor_SemanticError_Delete_OutsideOfClass1() - { - var src1 = @" -C() {} -"; - var src2 = @" -"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics(); + // The compiler interprets D() as a constructor declaration. + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -5288,6 +6884,56 @@ partial void C(int x) }); } + [Fact] + public void PartialDeclaration_Delete() + { + var srcA1 = "partial class C { public C() { } void F() { } }"; + var srcB1 = "partial class C { int x = 1; }"; + + var srcA2 = ""; + var srcB2 = "partial class C { int x = 2; void F() { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F"), partialType: "C"), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + }), + }); + } + + [Fact] + public void PartialDeclaration_Insert() + { + var srcA1 = ""; + var srcB1 = "partial class C { int x = 1; void F() { } }"; + + var srcA2 = "partial class C { public C() { } void F() { } }"; + var srcB2 = "partial class C { int x = 2; }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F"), partialType: "C"), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + }), + + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }), + }); + } + [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] public void Constructor_BlockBodyToExpressionBody() { @@ -5625,7 +7271,7 @@ public void FieldInitializerUpdate_StaticCtorUpdate1() edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single()) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) }); } [Fact] @@ -5638,7 +7284,7 @@ public void PropertyInitializerUpdate_StaticCtorUpdate1() edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single()) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) }); } [Fact] @@ -5650,7 +7296,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate_Private() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -5662,7 +7308,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate_Private() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -5675,7 +7321,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate_Public() edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } [Fact] @@ -5688,7 +7334,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate_Public() edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } [Fact] @@ -5948,7 +7594,6 @@ public void FieldInitializerUpdate_StackAllocInConstructor() edits.VerifyEdits( "Update [a = 1]@21 -> [a = 2]@21"); - // TODO (tomat): diagnostic should point to the field initializer edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc", FeaturesResources.constructor)); } @@ -6128,8 +7773,12 @@ public void FieldInitializerUpdate_PartialTypeWithSingleDeclaration() edits.VerifyEdits( "Update [a = 1]@22 -> [a = 2]@22"); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.PartialTypeInitializerUpdate, "a = 2", FeaturesResources.field)); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -6140,8 +7789,12 @@ public void PropertyInitializerUpdate_PartialTypeWithSingleDeclaration() var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.PartialTypeInitializerUpdate, "int a { get; } = 2;", FeaturesResources.auto_property)); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -6155,8 +7808,12 @@ public void FieldInitializerUpdate_PartialTypeWithMultipleDeclarations() edits.VerifyEdits( "Update [a = 1]@22 -> [a = 2]@22"); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.PartialTypeInitializerUpdate, "a = 2", FeaturesResources.field)); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -6167,8 +7824,12 @@ public void PropertyInitializerUpdate_PartialTypeWithMultipleDeclarations() var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.PartialTypeInitializerUpdate, "int a { get; } = 2;", FeaturesResources.auto_property)); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), preserveLocalVariables: true) + }); } [Fact] @@ -6803,36 +8464,83 @@ public C(bool b) } } "; - var src2 = @" -using System; + var src2 = @" +using System; + +class B +{ + public B(int a) { } +} + +class C : B +{ + static int F(Func x) => 1; + + int A = F(a => a + 1); + int B = F(b => b + 1); + + public C(bool b) + : base(F(c => c + 2)) + { + F(d => d + 1); + } +} +"; + var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(ctor => ctor.ToTestDisplayString() == "C..ctor(System.Boolean b)"), syntaxMap[0]) + }); + } + + [Fact] + public void FieldInitializerUpdate_Lambdas_PartialDeclarationDelete_SingleDocument() + { + var src1 = @" +partial class C +{ + int x = F(a => a + 1); +} + +partial class C +{ + int y = F(a => a + 10); +} + +partial class C +{ + public C() { } + static int F(Func x) => 1; +} +"; -class B + var src2 = @" +partial class C { - public B(int a) { } + int x = F(a => a + 1); } -class C : B +partial class C { - static int F(Func x) => 1; - - int A = F(a => a + 1); - int B = F(b => b + 1); + int y = F(a => a + 10); - public C(bool b) - : base(F(c => c + 2)) - { - F(d => d + 1); - } + static int F(Func x) => 1; } "; var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); edits.VerifySemantics( ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(ctor => ctor.ToTestDisplayString() == "C..ctor(System.Boolean b)"), syntaxMap[0]) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F"), partialType: "C"), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), syntaxMap[0], partialType: "C"), }); } @@ -6844,7 +8552,7 @@ public void FieldInitializerUpdate_ActiveStatements1() class C { - int A = 1; + int A = 1; int B = 1; public C(int a) { Console.WriteLine(1); } @@ -6856,7 +8564,7 @@ class C class C { - int A = 1; + int A = 1; int B = 2; public C(int a) { Console.WriteLine(1); } @@ -6907,11 +8615,33 @@ public C() { } edits.VerifySemantics(ActiveStatementsDescription.Empty, expectedSemanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => ((IPropertySymbol)c.GetMember("C").GetMembers("P").Skip(1).First()).GetMethod), + SemanticEdit(SemanticEditKind.Update, c => ((IPropertySymbol)c.GetMember("C").GetMembers("P").First()).GetMethod), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } + [Fact] + public void Field_Partial_DeleteInsert_InitializerRemoval() + { + var srcA1 = "partial class C { int F = 1; }"; + var srcB1 = "partial class C { }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int F; }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }), + }); + } + #endregion #region Fields @@ -7234,7 +8964,7 @@ public C() } [Fact] - public void FieldInsert_ParameterlessConstructorInsert_WithInitializersAndLambdas1() + public void FieldInsert_ConstructorReplacingImplicitConstructor_WithInitializersAndLambdas() { var src1 = @" using System; @@ -7256,7 +8986,7 @@ class C int A = F(a => a + 1); int B = F(b => b + 1); // new field - public C() // new ctor + public C() // new ctor replacing existing implicit constructor { F(c => c + 1); } @@ -7274,6 +9004,52 @@ class C }); } + [Fact, WorkItem(2504, "https://github.com/dotnet/roslyn/issues/2504")] + public void FieldInsert_ParameterlessConstructorInsert_WithInitializersAndLambdas() + { + var src1 = @" +using System; + +class C +{ + static int F(Func x) => 1; + + int A = F(a => a + 1); + + public C(int x) {} +} +"; + var src2 = @" +using System; + +class C +{ + static int F(Func x) => 1; + + int A = F(a => a + 1); + + public C(int x) {} + + public C() // new ctor + { + F(c => c + 1); + } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, "public C()")); + + // TODO (bug https://github.com/dotnet/roslyn/issues/2504): + //edits.VerifySemantics( + // ActiveStatementsDescription.Empty, + // new[] + // { + // SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").Constructors.Single(), syntaxMap[0]) + // }); + } + [Fact, WorkItem(2504, "https://github.com/dotnet/roslyn/issues/2504")] public void FieldInsert_ConstructorInsert_WithInitializersAndLambdas1() { @@ -7373,8 +9149,8 @@ public void FieldDelete1() "Delete [int a = 1]@10", "Delete [a = 1]@14"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", FeaturesResources.field)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.field, "a"))); } [Fact] @@ -7451,6 +9227,29 @@ public void EventFieldReorder() Diagnostic(RudeEditKind.Move, "event int c = 2", CSharpFeaturesResources.event_field)); } + [Fact] + public void EventField_Partial_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { event int E = 2; }"; + + var srcA2 = "partial class C { event int E = 2; }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }), + + DocumentResults(), + }); + } + #endregion #region Properties @@ -7471,6 +9270,20 @@ public void PropertyWithExpressionBody_Update() }); } + [Fact, WorkItem(48628, "https://github.com/dotnet/roslyn/issues/48628")] + public void PropertyWithExpressionBody_ModifierUpdate() + { + var src1 = "class C { int P => 1; }"; + var src2 = "class C { unsafe int P => 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [int P => 1;]@10 -> [unsafe int P => 1;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "unsafe int P", FeaturesResources.property_)); + } + [Fact] public void Property_ExpressionBodyToBlockBody1() { @@ -7545,7 +9358,7 @@ public void Property_BlockBodyToExpressionBody2() "Delete [set { }]@36"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "int P", CSharpFeaturesResources.property_setter)); + Diagnostic(RudeEditKind.Delete, "int P", DeletedSymbolDisplay(CSharpFeaturesResources.property_setter, "P.set"))); } [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] @@ -7600,6 +9413,38 @@ public void Property_GetterBlockBodyToGetterExpressionBody() }); } + [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] + public void Property_SetterBlockBodyToSetterExpressionBody() + { + var src1 = "class C { int P { set { } } }"; + var src2 = "class C { int P { set => F(); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [set { }]@18 -> [set => F();]@18"); + + edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").SetMethod), + }); + } + + [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] + public void Property_InitBlockBodyToInitExpressionBody() + { + var src1 = "class C { int P { init { } } }"; + var src2 = "class C { int P { init => F(); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [init { }]@18 -> [init => F();]@18"); + + edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").SetMethod, preserveLocalVariables: false), + }); + } + [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] public void Property_GetterExpressionBodyToGetterBlockBody() { @@ -7705,7 +9550,7 @@ public void PropertyReorder2() } [Fact] - public void PropertyAccessorReorder() + public void PropertyAccessorReorder_GetSet() { var src1 = "class C { int P { get { return 1; } set { } } }"; var src2 = "class C { int P { set { } get { return 1; } } }"; @@ -7718,6 +9563,20 @@ public void PropertyAccessorReorder() edits.VerifyRudeDiagnostics(); } + [Fact] + public void PropertyAccessorReorder_GetInit() + { + var src1 = "class C { int P { get { return 1; } init { } } }"; + var src2 = "class C { int P { init { } get { return 1; } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Reorder [init { }]@36 -> @18"); + + edits.VerifyRudeDiagnostics(); + } + [Fact] public void PropertyTypeUpdate() { @@ -7733,6 +9592,18 @@ public void PropertyTypeUpdate() Diagnostic(RudeEditKind.TypeUpdate, "char P", FeaturesResources.auto_property)); } + [Fact] + public void PropertyInsert() + { + var src1 = "class C { }"; + var src2 = "class C { int P { get => 1; set { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").GetMember("P"))); + } + [WorkItem(835827, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/835827")] [Fact] public void PropertyInsert_PInvoke() @@ -7750,18 +9621,22 @@ class C class C { - private static extern int P { [DllImport(""msvcrt.dll"")]get; } + private static extern int P1 { [DllImport(""x.dll"")]get; } + private static extern int P2 { [DllImport(""x.dll"")]set; } + private static extern int P3 { [DllImport(""x.dll"")]get; [DllImport(""x.dll"")]set; } } "; var edits = GetTopEdits(src1, src2); // CLR doesn't support methods without a body - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.InsertExtern, "private static extern int P", FeaturesResources.property_)); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertExtern, "private static extern int P1", FeaturesResources.property_), + Diagnostic(RudeEditKind.InsertExtern, "private static extern int P2", FeaturesResources.property_), + Diagnostic(RudeEditKind.InsertExtern, "private static extern int P3", FeaturesResources.property_)); } [Fact] - public void Property_InsertIntoStruct() + public void PropertyInsert_IntoStruct() { var src1 = @" struct S @@ -7862,7 +9737,7 @@ public void PrivatePropertyAccessorDelete() edits.VerifyEdits("Delete [set { _p = value; }]@44"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "int P", CSharpFeaturesResources.property_setter)); + Diagnostic(RudeEditKind.Delete, "int P", DeletedSymbolDisplay(CSharpFeaturesResources.property_setter, "P.set"))); } [Fact] @@ -7930,9 +9805,22 @@ public void PrivateAutoPropertyAccessorAdd6() edits.VerifyRudeDiagnostics(); } + [Fact] + public void PrivateAutoPropertyAccessorAdd_Init() + { + var src1 = "class C { int P { get; } = 1; }"; + var src2 = "class C { int P { get; init; } = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Insert [init;]@23"); + + edits.VerifyRudeDiagnostics(); + } + [WorkItem(755975, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/755975")] [Fact] - public void PrivateAutoPropertyAccessorDelete1() + public void PrivateAutoPropertyAccessorDelete_Get() { var src1 = "class C { int P { get; set; } }"; var src2 = "class C { int P { set; } }"; @@ -7942,21 +9830,67 @@ public void PrivateAutoPropertyAccessorDelete1() edits.VerifyEdits("Delete [get;]@18"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "int P", CSharpFeaturesResources.property_getter)); + Diagnostic(RudeEditKind.Delete, "int P", DeletedSymbolDisplay(CSharpFeaturesResources.property_getter, "P.get"))); + } + + [Fact] + public void AutoPropertyAccessor_SetToInit() + { + var src1 = "class C { int P { get; set; } }"; + var src2 = "class C { int P { get; init; } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [set;]@23 -> [init;]@23"); + + // not allowed since it changes the backing field readonly-ness and the signature of the setter (modreq) + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.AccessorKindUpdate, "init", CSharpFeaturesResources.property_setter)); + } + + [Fact] + public void AutoPropertyAccessor_InitToSet() + { + var src1 = "class C { int P { get; init; } }"; + var src2 = "class C { int P { get; set; } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [init;]@23 -> [set;]@23"); + + // not allowed since it changes the backing field readonly-ness and the signature of the setter (modreq) + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.AccessorKindUpdate, "set", CSharpFeaturesResources.property_setter)); } [Fact] - public void PrivateAutoPropertyAccessorDelete2() + public void PrivateAutoPropertyAccessorDelete_Set() { var src1 = "class C { int P { get; set; } = 1; }"; - var src2 = "class C { int P { set; } = 1; }"; + var src2 = "class C { int P { get; } = 1; }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Delete [get;]@18"); + edits.VerifyEdits("Delete [set;]@23"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "int P", DeletedSymbolDisplay(CSharpFeaturesResources.property_setter, "P.set"))); + } + + [Fact] + public void PrivateAutoPropertyAccessorDelete_Init() + { + var src1 = "class C { int P { get; init; } = 1; }"; + var src2 = "class C { int P { get; } = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Delete [init;]@23"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "int P", CSharpFeaturesResources.property_getter)); + Diagnostic(RudeEditKind.Delete, "int P", DeletedSymbolDisplay(CSharpFeaturesResources.property_setter, "P.init"))); } [Fact] @@ -7991,13 +9925,14 @@ public void InsertIncompleteProperty() public void Property_ReadOnlyRef_Insert() { var src1 = "class Test { }"; - var src2 = "class Test { ref readonly int M() { get; } }"; + var src2 = "class Test { ref readonly int P { get; } }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Insert [ref readonly int M() { get; }]@13", - "Insert [()]@31"); + "Insert [ref readonly int P { get; }]@13", + "Insert [{ get; }]@32", + "Insert [get;]@34"); edits.VerifyRudeDiagnostics(); } @@ -8005,16 +9940,122 @@ public void Property_ReadOnlyRef_Insert() [Fact] public void Property_ReadOnlyRef_Update() { - var src1 = "class Test { int M() { get; } }"; - var src2 = "class Test { ref readonly int M() { get; } }"; + var src1 = "class Test { int P { get; } }"; + var src2 = "class Test { ref readonly int P { get; } }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [int M() { get; }]@13 -> [ref readonly int M() { get; }]@13"); + "Update [int P { get; }]@13 -> [ref readonly int P { get; }]@13"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeUpdate, "ref readonly int M()", FeaturesResources.method)); + Diagnostic(RudeEditKind.TypeUpdate, "ref readonly int P", FeaturesResources.auto_property)); + } + + [Fact] + public void Property_Partial_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int P { get => 1; set { } } }"; + + var srcA2 = "partial class C { int P { get => 1; set { } } }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").SetMethod) + }), + + DocumentResults(), + }); + } + + [Fact] + public void PropertyInit_Partial_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int Q { get => 1; init { } }}"; + + var srcA2 = "partial class C { int Q { get => 1; init { } }}"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("Q").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("Q").SetMethod) + }), + + DocumentResults(), + }); + } + + [Fact] + public void AutoProperty_Partial_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int P { get; set; } int Q { get; init; } }"; + + var srcA2 = "partial class C { int P { get; set; } int Q { get; init; } }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(), + }); + } + + [Fact] + public void AutoPropertyWithInitializer_Partial_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int P { get; set; } = 1; }"; + + var srcA2 = "partial class C { int P { get; set; } = 1; }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }), + + DocumentResults(), + }); + } + + [Fact] + public void PropertyWithExpressionBody_Partial_InsertDeleteUpdate() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int P => 1; }"; + + var srcA2 = "partial class C { int P => 2; }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").GetMethod) }), + + DocumentResults(), + }); } #endregion @@ -8055,6 +10096,23 @@ public void Indexer_SetterUpdate() }); } + [Fact] + public void Indexer_InitUpdate() + { + var src1 = "class C { int this[int a] { get { return 1; } init { System.Console.WriteLine(value); } } }"; + var src2 = "class C { int this[int a] { get { return 1; } init { System.Console.WriteLine(value + 1); } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [init { System.Console.WriteLine(value); }]@46 -> [init { System.Console.WriteLine(value + 1); }]@46"); + + edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_Item"), preserveLocalVariables: false) + }); + } + [Fact] public void IndexerWithExpressionBody_Update() { @@ -8072,6 +10130,37 @@ public void IndexerWithExpressionBody_Update() }); } + [Fact, WorkItem(51297, "https://github.com/dotnet/roslyn/issues/51297")] + public void IndexerWithExpressionBody_Update_LiftedParameter() + { + // TODO: https://github.com/dotnet/roslyn/issues/51297 + // The test fails if "+ 10" and "+ 11" are removed. + + var src1 = @" +using System; + +class C +{ + int this[int a] => new Func(() => a + 1)() + 10; +} +"; + var src2 = @" +using System; + +class C +{ + int this[int a] => new Func(() => 2)() + 11; // not capturing a anymore +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [int this[int a] => new Func(() => a + 1)() + 10;]@35 -> [int this[int a] => new Func(() => 2)() + 11;]@35"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.NotCapturingVariable, "a", "a")); + } + [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] public void Indexer_ExpressionBodyToBlockBody() { @@ -8190,6 +10279,40 @@ public void Indexer_GetterBlockBodyToGetterExpressionBody() }); } + [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] + public void Indexer_SetterBlockBodyToSetterExpressionBody() + { + var src1 = "class C { int this[int a] { set { } } void F() { } }"; + var src2 = "class C { int this[int a] { set => F(); } void F() { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [set { }]@28 -> [set => F();]@28"); + + edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_Item")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")), + }); + } + + [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] + public void Indexer_InitBlockBodyToInitExpressionBody() + { + var src1 = "class C { int this[int a] { init { } } void F() { } }"; + var src2 = "class C { int this[int a] { init => F(); } void F() { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [init { }]@28 -> [init => F();]@28"); + + edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_Item")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")), + }); + } + [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] public void Indexer_GetterExpressionBodyToGetterBlockBody() { @@ -8221,7 +10344,7 @@ public void Indexer_GetterAndSetterBlockBodiesToExpressionBody() "Delete [set { Console.WriteLine(0); }]@46"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "int this[int a]", CSharpFeaturesResources.indexer_setter)); + Diagnostic(RudeEditKind.Delete, "int this[int a]", DeletedSymbolDisplay(CSharpFeaturesResources.indexer_setter, "this[int].set"))); } [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] @@ -8416,54 +10539,50 @@ public T this[int i] edits.VerifyEdits("Insert [get { return arr[i]; }]@304"); - edits.VerifyRudeDiagnostics(); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.InsertIntoGenericType, "get", CSharpFeaturesResources.indexer_getter)); } [Fact] public void Indexer_AddSetAccessor() { var src1 = @" -class Test -{ - static void Main(string[] args) - { - SampleCollection stringCollection = new SampleCollection(); - System.Console.Write(stringCollection[0]); - } -} - -class SampleCollection +class C { - private T[] arr = new T[100]; - public T this[int i] - { - get { return arr[i]; } - } + public int this[int i] { get { return default; } } }"; var src2 = @" -class Test +class C { - static void Main(string[] args) - { - SampleCollection stringCollection = new SampleCollection(); - System.Console.Write(stringCollection[0]); - } -} + public int this[int i] { get { return default; } set { } } +}"; + var edits = GetTopEdits(src1, src2); -class SampleCollection + edits.VerifyEdits("Insert [set { }]@67"); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").GetMember("this[]").SetMethod)); + } + + [Fact] + public void Indexer_AddSetAccessor_GenericType() + { + var src1 = @" +class C { - private T[] arr = new T[100]; - public T this[int i] - { - get { return arr[i]; } - set { arr[i] = value; } - } + public T this[int i] { get { return default; } } +}"; + var src2 = @" +class C +{ + public T this[int i] { get { return default; } set { } } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Insert [set { arr[i] = value; }]@348"); + edits.VerifyEdits("Insert [set { }]@68"); - edits.VerifyRudeDiagnostics(); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertIntoGenericType, "set", CSharpFeaturesResources.indexer_setter)); } [WorkItem(750109, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/750109")] @@ -8471,18 +10590,8 @@ public T this[int i] public void Indexer_DeleteGetAccessor() { var src1 = @" -class Test -{ - static void Main(string[] args) - { - SampleCollection stringCollection = new SampleCollection(); - stringCollection[0] = ""hello""; - } -} - -class SampleCollection +class C { - private T[] arr = new T[100]; public T this[int i] { get { return arr[i]; } @@ -8490,18 +10599,8 @@ public T this[int i] } }"; var src2 = @" -class Test -{ - static void Main(string[] args) - { - SampleCollection stringCollection = new SampleCollection(); - stringCollection[0] = ""hello""; - } -} - -class SampleCollection +class C { - private T[] arr = new T[100]; public T this[int i] { set { arr[i] = value; } @@ -8509,58 +10608,31 @@ public T this[int i] }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Delete [get { return arr[i]; }]@304"); + edits.VerifyEdits("Delete [get { return arr[i]; }]@58"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "public T this[int i]", CSharpFeaturesResources.indexer_getter)); + Diagnostic(RudeEditKind.Delete, "public T this[int i]", DeletedSymbolDisplay(CSharpFeaturesResources.indexer_getter, "this[int].get"))); } [Fact] public void Indexer_DeleteSetAccessor() { var src1 = @" -class Test -{ - static void Main(string[] args) - { - SampleCollection stringCollection = new SampleCollection(); - stringCollection[0] = ""hello""; - } -} - -class SampleCollection +class C { - private T[] arr = new T[100]; - public T this[int i] - { - get { return arr[i]; } - set { arr[i] = value; } - } + public int this[int i] { get { return 0; } set { } } }"; var src2 = @" -class Test -{ - static void Main(string[] args) - { - SampleCollection stringCollection = new SampleCollection(); - stringCollection[0] = ""hello""; - } -} - -class SampleCollection +class C { - private T[] arr = new T[100]; - public T this[int i] - { - get { return arr[i]; } - } + public int this[int i] { get { return 0; } } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Delete [set { arr[i] = value; }]@336"); + edits.VerifyEdits("Delete [set { }]@61"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "public T this[int i]", CSharpFeaturesResources.indexer_setter)); + Diagnostic(RudeEditKind.Delete, "public int this[int i]", DeletedSymbolDisplay(CSharpFeaturesResources.indexer_setter, "this[int].set"))); } [Fact, WorkItem(1174850, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1174850")] @@ -8678,6 +10750,107 @@ public void Indexer_ReadOnlyRef_ReturnType_Update() Diagnostic(RudeEditKind.TypeUpdate, "ref readonly int this[int i]", FeaturesResources.indexer_)); } + [Fact] + public void Indexer_Partial_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int this[int x] { get => 1; set { } } }"; + + var srcA2 = "partial class C { int this[int x] { get => 1; set { } } }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").SetMethod) + }), + + DocumentResults(), + }); + } + + [Fact] + public void IndexerInit_Partial_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int this[int x] { get => 1; init { } }}"; + + var srcA2 = "partial class C { int this[int x] { get => 1; init { } }}"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").SetMethod) + }), + + DocumentResults(), + }); + } + + [Fact] + public void AutoIndexer_Partial_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int this[int x] { get; set; } int Q { get; init; } }"; + + var srcA2 = "partial class C { int this[int x] { get; set; } int Q { get; init; } }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(), + }); + } + + [Fact, WorkItem(51297, "https://github.com/dotnet/roslyn/issues/51297")] + public void IndexerWithExpressionBody_Partial_InsertDeleteUpdate_LiftedParameter() + { + // TODO: https://github.com/dotnet/roslyn/issues/51297 + // The test fails if "+ 10" and "+ 11" are removed. + + var srcA1 = @" +partial class C +{ +}"; + var srcB1 = @" +partial class C +{ + int this[int a] => new System.Func(() => a + 1) + 10; +}"; + + var srcA2 = @" +partial class C +{ + int this[int a] => new System.Func(() => 2) + 11; // no capture +}"; + var srcB2 = @" +partial class C +{ +}"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(diagnostics: new[] { Diagnostic(RudeEditKind.NotCapturingVariable, "a", "a") }), + DocumentResults(), + }); + } + #endregion #region Events @@ -8725,6 +10898,30 @@ public void EventAccessorReorder3() "Reorder [remove { }]@33 -> @64"); } + [Fact] + public void EventInsert() + { + var src1 = "class C { }"; + var src2 = "class C { event int E { remove { } add { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").GetMember("E"))); + } + + [Fact] + public void EventDelete() + { + var src1 = "class C { event int E { remove { } add { } } }"; + var src2 = "class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.event_, "E"))); + } + [Fact] public void EventInsert_IntoLayoutClass_Sequential() { @@ -8807,6 +11004,30 @@ public class C edits.VerifySemanticDiagnostics(); } + [Fact] + public void Event_Partial_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { event int E { add { } remove { } } }"; + + var srcA2 = "partial class C { event int E { add { } remove { } } }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("E").AddMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("E").RemoveMethod) + }), + + DocumentResults(), + }); + } + #endregion #region Parameter diff --git a/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj b/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj index d4b4827ca2ec6..9d45d519b36c2 100644 --- a/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj +++ b/src/EditorFeatures/CSharpTest/Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj @@ -75,5 +75,5 @@ - + diff --git a/src/EditorFeatures/Core/Implementation/CommentSelection/ToggleLineCommentCommandHandler.cs b/src/EditorFeatures/Core/Implementation/CommentSelection/ToggleLineCommentCommandHandler.cs index aedf78354e041..2bf10c1d70a04 100644 --- a/src/EditorFeatures/Core/Implementation/CommentSelection/ToggleLineCommentCommandHandler.cs +++ b/src/EditorFeatures/Core/Implementation/CommentSelection/ToggleLineCommentCommandHandler.cs @@ -21,6 +21,7 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Microsoft.VisualStudio.Text.Operations; using Roslyn.Utilities; @@ -88,6 +89,8 @@ private static CommentSelectionResult ToggleLineComment(CommentSelectionInfo com span => span, span => GetLinesFromSelectedSpan(span).ToImmutableArray()); + var isMultiCaret = selectedSpans.Count > 1; + Operation operation; // If any of the lines are uncommented, add comments. if (linesInSelections.Values.Any(lines => SelectionHasUncommentedLines(lines, commentInfo))) @@ -103,7 +106,7 @@ private static CommentSelectionResult ToggleLineComment(CommentSelectionInfo com { foreach (var selection in linesInSelections) { - UncommentLines(selection.Value, textChanges, trackingSpans, commentInfo); + UncommentLines(selection.Key, selection.Value, textChanges, trackingSpans, commentInfo); } operation = Operation.Uncomment; @@ -112,8 +115,12 @@ private static CommentSelectionResult ToggleLineComment(CommentSelectionInfo com return new CommentSelectionResult(textChanges, trackingSpans, operation); } - private static void UncommentLines(ImmutableArray commentedLines, ArrayBuilder textChanges, - ArrayBuilder trackingSpans, CommentSelectionInfo commentInfo) + private static void UncommentLines( + SnapshotSpan selectedSpan, + ImmutableArray commentedLines, + ArrayBuilder textChanges, + ArrayBuilder trackingSpans, + CommentSelectionInfo commentInfo) { foreach (var line in commentedLines) { @@ -126,11 +133,16 @@ private static void UncommentLines(ImmutableArray commentedLi } } - trackingSpans.Add(new CommentTrackingSpan(TextSpan.FromBounds(commentedLines.First().Start, commentedLines.Last().End))); + var commentTrackingSpan = new CommentTrackingSpan(selectedSpan.Span.ToTextSpan()); + trackingSpans.Add(commentTrackingSpan); } - private static void CommentLines(SnapshotSpan selectedSpan, ImmutableArray linesInSelection, - ArrayBuilder textChanges, ArrayBuilder trackingSpans, CommentSelectionInfo commentInfo) + private static void CommentLines( + SnapshotSpan selectedSpan, + ImmutableArray linesInSelection, + ArrayBuilder textChanges, + ArrayBuilder trackingSpans, + CommentSelectionInfo commentInfo) { var indentation = DetermineSmallestIndent(selectedSpan, linesInSelection.First(), linesInSelection.Last()); foreach (var line in linesInSelection) @@ -141,8 +153,8 @@ private static void CommentLines(SnapshotSpan selectedSpan, ImmutableArray GetLinesFromSelectedSpan(SnapshotSpan span) diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs index 0b561853b1862..6002a22d63a53 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs @@ -349,9 +349,14 @@ public Task WriteStreamAsync(Document document, string name, Stream stream return SpecializedTasks.True; } - public virtual void Dispose() + public void Dispose() { } + + public ValueTask DisposeAsync() + { + return ValueTaskFactory.CompletedTask; + } } } } diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index e3767c42678d7..222dcec20922c 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -900,36 +900,28 @@ public async Task BreakMode_FileAdded() var service = CreateEditAndContinueService(workspace); StartDebuggingSession(service); - StartEditSession(service, loadedModules: new MockManagedEditAndContinueDebuggerService() - { - IsEditAndContinueAvailable = _ => new ManagedEditAndContinueAvailability(ManagedEditAndContinueAvailabilityStatus.NotSupportedForClr64Version, "*message*") - }); + StartEditSession(service); // add a source file: - var documentB = project.AddDocument("file2.cs", SourceText.From(sourceB), filePath: sourceFileB.Path); + var documentB = project.AddDocument("file2.cs", SourceText.From(sourceB, Encoding.UTF8), filePath: sourceFileB.Path); workspace.ChangeSolution(documentB.Project.Solution); + documentB = workspace.CurrentSolution.GetDocument(documentB.Id); var diagnostics2 = await service.GetDocumentDiagnosticsAsync(documentB, s_noDocumentActiveSpans, CancellationToken.None).ConfigureAwait(false); - AssertEx.Equal( - new[] { "ENC0071: " + string.Format(FeaturesResources.Adding_a_new_file_will_prevent_the_debug_session_from_continuing) }, - diagnostics2.Select(d => $"{d.Id}: {d.GetMessage()}")); + Assert.Empty(diagnostics2); Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None).ConfigureAwait(false)); var (updates, emitDiagnostics) = await service.EmitSolutionUpdateAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, CancellationToken.None).ConfigureAwait(false); - Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); - Assert.Empty(updates.Updates); - AssertEx.Equal(new[] { $"{project.Id} Error ENC2010: {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, project.Name, "*message*")}" }, InspectDiagnostics(emitDiagnostics)); + Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); - EndEditSession(service, documentsWithRudeEdits: ImmutableArray.Create(documentB.Id)); + EndEditSession(service); EndDebuggingSession(service); AssertEx.Equal(new[] { "Debugging_EncSession: SessionId=1|SessionCount=1|EmptySessionCount=0", - "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=1", - "Debugging_EncSession_EditSession_EmitDeltaErrorId: SessionId=1|EditSessionId=2|ErrorId=ENC2010", - "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=71|RudeEditSyntaxKind=0|RudeEditBlocking=True" + "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=0" }, _telemetryLog); } diff --git a/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs b/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs index cd9a2f015092c..4643bb76e6670 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs @@ -28,8 +28,6 @@ public void ToDiagnostic() { RudeEditKind.ActiveStatementUpdate, RudeEditKind.PartiallyExecutedActiveStatementUpdate, - RudeEditKind.PartiallyExecutedActiveStatementDelete, - RudeEditKind.DeleteActiveStatement, RudeEditKind.UpdateExceptionHandlerOfActiveTry, RudeEditKind.UpdateTryOrCatchWithActiveFinally, RudeEditKind.UpdateCatchHandlerAroundActiveStatement, @@ -39,7 +37,6 @@ public void ToDiagnostic() RudeEditKind.MethodKindUpdate, RudeEditKind.DeclareLibraryUpdate, RudeEditKind.DeclareAliasUpdate, - RudeEditKind.ChangingConstructorVisibility, RudeEditKind.InsertDllImport, RudeEditKind.MethodBodyAdd, RudeEditKind.MethodBodyDelete, diff --git a/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs b/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs index 5d7b4aef25381..4db4b72a73af6 100644 --- a/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs +++ b/src/EditorFeatures/Test/Preview/PreviewWorkspaceTests.cs @@ -127,7 +127,7 @@ public async Task TestPreviewServices() var persistentService = previewWorkspace.Services.GetRequiredService(); - using var storage = await persistentService.GetStorageAsync(previewWorkspace.CurrentSolution, CancellationToken.None); + await using var storage = await persistentService.GetStorageAsync(previewWorkspace.CurrentSolution, CancellationToken.None); Assert.IsType(storage); } diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb index 2f1e8761c5f16..a844959deb7a0 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.vb @@ -51,10 +51,11 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Await TestStreamingFeature(element, searchSingleFileOnly, uiVisibleOnly, host) End Function - Private Shared Async Function TestStreamingFeature(element As XElement, - searchSingleFileOnly As Boolean, - uiVisibleOnly As Boolean, - host As TestHost) As Task + Private Shared Async Function TestStreamingFeature( + element As XElement, + searchSingleFileOnly As Boolean, + uiVisibleOnly As Boolean, + host As TestHost) As Task ' We don't support testing features that only expect partial results. If searchSingleFileOnly OrElse uiVisibleOnly Then Return @@ -254,7 +255,19 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.FindReferences Optional uiVisibleOnly As Boolean = False, Optional options As FindReferencesSearchOptions = Nothing) As Task + Await TestAPI(definition, host, explicit:=False, searchSingleFileOnly, uiVisibleOnly, options) + Await TestAPI(definition, host, explicit:=True, searchSingleFileOnly, uiVisibleOnly, options) + End Function + + Private Async Function TestAPI( + definition As XElement, + host As TestHost, + explicit As Boolean, + searchSingleFileOnly As Boolean, + uiVisibleOnly As Boolean, + options As FindReferencesSearchOptions) As Task options = If(options, FindReferencesSearchOptions.Default) + options = options.With(explicit:=explicit) Using workspace = TestWorkspace.Create(definition, composition:=s_composition.WithTestHostParts(host)) workspace.SetTestLogger(AddressOf _outputHelper.WriteLine) diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/DocumentAnalysisResultsDescription.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/DocumentAnalysisResultsDescription.cs new file mode 100644 index 0000000000000..bb569665adeb3 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/DocumentAnalysisResultsDescription.cs @@ -0,0 +1,42 @@ +// 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.EditAndContinue.UnitTests +{ + internal readonly struct DocumentAnalysisResultsDescription + { + public readonly ActiveStatementsDescription ActiveStatements; + + /// + /// Default if semantic edits are not validated by the test. + /// + public readonly ImmutableArray SemanticEdits; + + public readonly ImmutableArray Diagnostics; + + public DocumentAnalysisResultsDescription( + ActiveStatementsDescription? activeStatements = null, + SemanticEditDescription[]? semanticEdits = null, + RudeEditDiagnosticDescription[]? diagnostics = null) + { + // The test must validate semantic edits, diagnostics or both. + // If neither is specified then assume the expectation is that + // the documents has no edits and no diagnostics. + if (semanticEdits is null && diagnostics is null) + { + SemanticEdits = ImmutableArray.Empty; + Diagnostics = ImmutableArray.Empty; + } + else + { + SemanticEdits = semanticEdits.AsImmutableOrNull(); + Diagnostics = diagnostics.AsImmutableOrEmpty(); + } + + ActiveStatements = activeStatements ?? ActiveStatementsDescription.Empty; + } + } +} diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index ce4c509d7be6a..14fc87873715e 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -5,10 +5,16 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Composition; +using System.Diagnostics; +using System.IO; using System.Linq; +using System.Text; using System.Threading; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; @@ -26,8 +32,9 @@ internal abstract class EditAndContinueTestHelpers public abstract AbstractEditAndContinueAnalyzer Analyzer { get; } public abstract SyntaxNode FindNode(SyntaxNode root, TextSpan span); public abstract SyntaxTree ParseText(string source); - public abstract Compilation CreateLibraryCompilation(string name, IEnumerable trees); public abstract ImmutableArray GetDeclarators(ISymbol method); + public abstract string LanguageName { get; } + public abstract TreeComparer TopSyntaxComparer { get; } internal void VerifyUnchangedDocument( string source, @@ -57,92 +64,46 @@ internal void VerifyUnchangedDocument( actualNewExceptionRegions); // check active statements: - AssertSpansEqual(expectedNewActiveStatements, actualNewActiveStatements.Select(s => s.Span), source, text); + AssertSpansEqual(expectedNewActiveStatements, actualNewActiveStatements.Select(s => s.Span), text); // check new exception regions: Assert.Equal(expectedNewExceptionRegions.Length, actualNewExceptionRegions.Count); for (var i = 0; i < expectedNewExceptionRegions.Length; i++) { - AssertSpansEqual(expectedNewExceptionRegions[i], actualNewExceptionRegions[i], source, text); + AssertSpansEqual(expectedNewExceptionRegions[i], actualNewExceptionRegions[i], text); } } - internal void VerifyRudeDiagnostics( - EditScript editScript, + private void VerifyActiveStatementsAndExceptionRegions( ActiveStatementsDescription description, - RudeEditDiagnosticDescription[] expectedDiagnostics) + IReadOnlyList oldActiveStatements, + SourceText oldText, + SourceText newText, + SyntaxNode oldRoot, + IReadOnlyList actualNewActiveStatements, + IReadOnlyList> actualNewExceptionRegions) { - var oldActiveStatements = description.OldStatements; - - if (description.OldTrackingSpans != null) - { - Assert.Equal(oldActiveStatements.Length, description.OldTrackingSpans.Length); - } - - var newSource = editScript.Match.NewRoot.SyntaxTree.ToString(); - var oldSource = editScript.Match.OldRoot.SyntaxTree.ToString(); - - var oldText = SourceText.From(oldSource); - var newText = SourceText.From(newSource); - - var diagnostics = new ArrayBuilder(); - var updatedActiveMethodMatches = new ArrayBuilder(); - var actualNewActiveStatements = ImmutableArray.CreateBuilder(oldActiveStatements.Length); - actualNewActiveStatements.Count = actualNewActiveStatements.Capacity; - var actualNewExceptionRegions = ImmutableArray.CreateBuilder>(oldActiveStatements.Length); - actualNewExceptionRegions.Count = actualNewExceptionRegions.Capacity; - var editMap = BuildEditMap(editScript); - - var documentId = DocumentId.CreateNewId(ProjectId.CreateNewId("TestEnCProject"), "TestEnCDocument"); - var testAccessor = Analyzer.GetTestAccessor(); - - testAccessor.AnalyzeMemberBodiesSyntax( - editScript, - editMap, - oldText, - newText, - oldActiveStatements.AsImmutable(), - description.OldTrackingSpans.ToImmutableArrayOrEmpty(), - actualNewActiveStatements, - actualNewExceptionRegions, - updatedActiveMethodMatches, - diagnostics); - - testAccessor.ReportTopLevelSynctactiveRudeEdits(diagnostics, editScript, editMap); - - diagnostics.Verify(newSource, expectedDiagnostics); - // check active statements: - AssertSpansEqual(description.NewSpans, actualNewActiveStatements.Select(s => s.Span), newSource, newText); + AssertSpansEqual(description.NewSpans, actualNewActiveStatements.Select(s => s.Span), newText); - if (diagnostics.Count == 0) + // check old exception regions: + for (var i = 0; i < oldActiveStatements.Count; i++) { - // check old exception regions: - for (var i = 0; i < oldActiveStatements.Length; i++) - { - var actualOldExceptionRegions = Analyzer.GetExceptionRegions( - oldText, - editScript.Match.OldRoot, - oldActiveStatements[i].Span, - isNonLeaf: oldActiveStatements[i].IsNonLeaf, - out _); - - AssertSpansEqual(description.OldRegions[i], actualOldExceptionRegions, oldSource, oldText); - } + var oldRegions = Analyzer.GetExceptionRegions( + oldText, + oldRoot, + oldActiveStatements[i].Span, + isNonLeaf: oldActiveStatements[i].IsNonLeaf, + out _); - // check new exception regions: - Assert.Equal(description.NewRegions.Length, actualNewExceptionRegions.Count); - for (var i = 0; i < description.NewRegions.Length; i++) - { - AssertSpansEqual(description.NewRegions[i], actualNewExceptionRegions[i], newSource, newText); - } + AssertSpansEqual(description.OldRegions[i], oldRegions, oldText); } - else + + // check new exception regions: + Assert.Equal(description.NewRegions.Length, actualNewExceptionRegions.Count); + for (var i = 0; i < description.NewRegions.Length; i++) { - for (var i = 0; i < oldActiveStatements.Length; i++) - { - Assert.Equal(0, description.NewRegions[i].Length); - } + AssertSpansEqual(description.NewRegions[i], actualNewExceptionRegions[i], newText); } } @@ -174,7 +135,7 @@ internal void VerifyLineEdits( diagnostics, default); - diagnostics.Verify(newSource, expectedDiagnostics); + VerifyDiagnostics(expectedDiagnostics, diagnostics, newText); AssertEx.Equal(expectedLineEdits, actualLineEdits, itemSeparator: ",\r\n"); @@ -182,102 +143,97 @@ internal void VerifyLineEdits( AssertEx.Equal(expectedNodeUpdates, actualNodeUpdates, itemSeparator: ",\r\n"); } - internal void VerifySemantics( - IEnumerable> editScripts, - ActiveStatementsDescription? activeStatements = null, - SemanticEditDescription[]? expectedSemanticEdits = null, - RudeEditDiagnosticDescription[]? expectedDiagnostics = null) + internal void VerifySemantics(EditScript[] editScripts, TargetFramework targetFramework, DocumentAnalysisResultsDescription[] expectedResults) { - activeStatements ??= ActiveStatementsDescription.Empty; + Assert.True(editScripts.Length == expectedResults.Length); + var documentCount = expectedResults.Length; - var oldTrees = editScripts.Select(editScript => editScript.Match.OldRoot.SyntaxTree).ToArray(); - var newTrees = editScripts.Select(editScript => editScript.Match.NewRoot.SyntaxTree).ToArray(); + using var workspace = new AdhocWorkspace(FeaturesTestCompositions.Features.GetHostServices()); + CreateProjects(editScripts, workspace, targetFramework, out var oldProject, out var newProject); - var oldCompilation = CreateLibraryCompilation("Old", oldTrees); - var newCompilation = CreateLibraryCompilation("New", newTrees); + var oldDocuments = oldProject.Documents.ToArray(); + var newDocuments = newProject.Documents.ToArray(); - var oldActiveStatements = activeStatements.OldStatements.AsImmutable(); - var triviaEdits = new ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)>(); - var actualLineEdits = new ArrayBuilder(); - var actualSemanticEdits = new ArrayBuilder(); - var includeFirstLineInDiagnostics = expectedDiagnostics?.Any(d => d.FirstLine != null) == true; - var actualDiagnosticDescriptions = new ArrayBuilder(); - var actualDeclarationErrors = new ArrayBuilder(); - - var actualNewActiveStatements = ImmutableArray.CreateBuilder(activeStatements.OldStatements.Length); - actualNewActiveStatements.Count = actualNewActiveStatements.Capacity; + Debug.Assert(oldDocuments.Length == newDocuments.Length); - var actualNewExceptionRegions = ImmutableArray.CreateBuilder>(activeStatements.OldStatements.Length); - actualNewExceptionRegions.Count = actualNewExceptionRegions.Capacity; + var oldTrees = oldDocuments.Select(d => d.GetSyntaxTreeSynchronously(default)!).ToArray(); + var newTrees = newDocuments.Select(d => d.GetSyntaxTreeSynchronously(default)!).ToArray(); var testAccessor = Analyzer.GetTestAccessor(); - foreach (var editScript in editScripts) + for (var documentIndex = 0; documentIndex < documentCount; documentIndex++) { - var oldRoot = editScript.Match.OldRoot; - var newRoot = editScript.Match.NewRoot; - var oldSource = oldRoot.SyntaxTree.ToString(); - var newSource = newRoot.SyntaxTree.ToString(); - - var editMap = BuildEditMap(editScript); - var oldText = SourceText.From(oldSource); - var newText = SourceText.From(newSource); - var oldModel = oldCompilation.GetSemanticModel(oldRoot.SyntaxTree); - var newModel = newCompilation.GetSemanticModel(newRoot.SyntaxTree); - - var diagnostics = new ArrayBuilder(); - var updatedActiveMethodMatches = new ArrayBuilder(); - - testAccessor.AnalyzeMemberBodiesSyntax( - editScript, - editMap, - oldText, - newText, - oldActiveStatements, - activeStatements.OldTrackingSpans.ToImmutableArrayOrEmpty(), - actualNewActiveStatements, - actualNewExceptionRegions, - updatedActiveMethodMatches, - diagnostics); + var expectedResult = expectedResults[documentIndex]; - testAccessor.ReportTopLevelSynctactiveRudeEdits(diagnostics, editScript, editMap); + var oldActiveStatements = expectedResult.ActiveStatements.OldStatements.ToImmutableArray(); - testAccessor.AnalyzeTrivia( - oldText, - newText, - editScript.Match, - editMap, - triviaEdits, - actualLineEdits, - diagnostics, - CancellationToken.None); - - testAccessor.AnalyzeSemantics( - editScript, - editMap, - oldText, - oldActiveStatements, - triviaEdits, - updatedActiveMethodMatches, - oldModel, - newModel, - actualSemanticEdits, - diagnostics, - CancellationToken.None); - - actualDiagnosticDescriptions.AddRange(diagnostics.ToDescription(newSource, includeFirstLineInDiagnostics)); - } + var includeFirstLineInDiagnostics = expectedResult.Diagnostics.Any(d => d.FirstLine != null) == true; + var newActiveStatementSpans = expectedResult.ActiveStatements.OldTrackingSpans.ToImmutableArrayOrEmpty(); - actualDiagnosticDescriptions.Verify(expectedDiagnostics); + // we need to rebuild the edit script, so that it operates on nodes associated with the same syntax trees backing the documents: + var oldTree = oldTrees[documentIndex]; + var newTree = newTrees[documentIndex]; + var oldRoot = oldTree.GetRoot(); + var newRoot = newTree.GetRoot(); - if (expectedSemanticEdits == null) - { - return; + var oldDocument = oldDocuments[documentIndex]; + var newDocument = newDocuments[documentIndex]; + + var oldModel = oldDocument.GetSemanticModelAsync().Result; + var newModel = newDocument.GetSemanticModelAsync().Result; + Contract.ThrowIfNull(oldModel); + Contract.ThrowIfNull(newModel); + + var result = Analyzer.AnalyzeDocumentAsync(oldProject, oldActiveStatements, newDocument, newActiveStatementSpans, CancellationToken.None).Result; + + var oldText = oldDocument.GetTextSynchronously(default); + var newText = newDocument.GetTextSynchronously(default); + + VerifyDiagnostics(expectedResult.Diagnostics, result.RudeEditErrors.ToDescription(newText, includeFirstLineInDiagnostics)); + + if (!expectedResult.SemanticEdits.IsDefault) + { + VerifySemanticEdits(expectedResult.SemanticEdits, result.SemanticEdits, oldModel.Compilation, newModel.Compilation, oldRoot, newRoot); + } + + if (expectedResult.Diagnostics.IsEmpty) + { + VerifyActiveStatementsAndExceptionRegions( + expectedResult.ActiveStatements, + oldActiveStatements, + oldText, + newText, + oldRoot, + result.ActiveStatements, + result.ExceptionRegions); + } + else + { + Assert.True(result.ExceptionRegions.IsDefault); + } } + } - Assert.Equal(expectedSemanticEdits.Length, actualSemanticEdits.Count); + public static void VerifyDiagnostics(IEnumerable expected, IEnumerable actual, SourceText newSource) + => VerifyDiagnostics(expected, actual.ToDescription(newSource, expected.Any(d => d.FirstLine != null))); - for (var i = 0; i < actualSemanticEdits.Count; i++) + public static void VerifyDiagnostics(IEnumerable expected, IEnumerable actual) + => AssertEx.SetEqual(expected, actual, itemSeparator: ",\r\n"); + + private void VerifySemanticEdits( + ImmutableArray expectedSemanticEdits, + ImmutableArray actualSemanticEdits, + Compilation oldCompilation, + Compilation newCompilation, + SyntaxNode oldRoot, + SyntaxNode newRoot) + { + // string comparison to simplify understanding why a test failed: + AssertEx.Equal( + expectedSemanticEdits.Select(e => $"{e.Kind}: {e.SymbolProvider(newCompilation)}"), + actualSemanticEdits.NullToEmpty().Select(e => $"{e.Kind}: {e.Symbol.Resolve(newCompilation).Symbol}")); + + for (var i = 0; i < actualSemanticEdits.Length; i++) { var editKind = expectedSemanticEdits[i].Kind; @@ -285,24 +241,32 @@ internal void VerifySemantics( var expectedOldSymbol = (editKind == SemanticEditKind.Update) ? expectedSemanticEdits[i].SymbolProvider(oldCompilation) : null; var expectedNewSymbol = expectedSemanticEdits[i].SymbolProvider(newCompilation); - var actualOldSymbol = actualSemanticEdits[i].OldSymbol; - var actualNewSymbol = actualSemanticEdits[i].NewSymbol; + var symbolKey = actualSemanticEdits[i].Symbol; - Assert.Equal(expectedOldSymbol, actualOldSymbol); - Assert.Equal(expectedNewSymbol, actualNewSymbol); + if (editKind == SemanticEditKind.Update) + { + Assert.Equal(expectedOldSymbol, symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true).Symbol); + Assert.Equal(expectedNewSymbol, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); + } + else if (editKind == SemanticEditKind.Insert) + { + Assert.Equal(expectedNewSymbol, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); + } + else + { + Assert.False(true, "Only Update or Insert allowed"); + } - var expectedSyntaxMap = expectedSemanticEdits[i].SyntaxMap; - var syntaxTreeOrdinal = expectedSemanticEdits[i].SyntaxTreeOrdinal; - var oldRoot = oldTrees[syntaxTreeOrdinal].GetRoot(); - var newRoot = newTrees[syntaxTreeOrdinal].GetRoot(); + // Edit is expected to have a syntax map: var actualSyntaxMap = actualSemanticEdits[i].SyntaxMap; + Assert.Equal(expectedSemanticEdits[i].HasSyntaxMap, actualSyntaxMap != null); - Assert.Equal(expectedSemanticEdits[i].PreserveLocalVariables, actualSemanticEdits[i].PreserveLocalVariables); + // If expected map is specified validate its mappings with the actual one: + var expectedSyntaxMap = expectedSemanticEdits[i].SyntaxMap; if (expectedSyntaxMap != null) { Contract.ThrowIfNull(actualSyntaxMap); - Assert.True(expectedSemanticEdits[i].PreserveLocalVariables); foreach (var expectedSpanMapping in expectedSyntaxMap) { @@ -313,24 +277,41 @@ internal void VerifySemantics( Assert.Equal(expectedOldNode, actualOldNode); } } - else if (!expectedSemanticEdits[i].PreserveLocalVariables) - { - Assert.Null(actualSyntaxMap); - } } } - private static void AssertSpansEqual(IEnumerable expected, IEnumerable actual, string newSource, SourceText newText) + private void CreateProjects(EditScript[] editScripts, AdhocWorkspace workspace, TargetFramework targetFramework, out Project oldProject, out Project newProject) + { + oldProject = workspace.AddProject("project", LanguageName).WithMetadataReferences(TargetFrameworkUtil.GetReferences(targetFramework)); + var documentIndex = 0; + foreach (var editScript in editScripts) + { + oldProject = oldProject.AddDocument(documentIndex.ToString(), editScript.Match.OldRoot).Project; + documentIndex++; + } + + var newSolution = oldProject.Solution; + documentIndex = 0; + foreach (var oldDocument in oldProject.Documents) + { + newSolution = newSolution.WithDocumentSyntaxRoot(oldDocument.Id, editScripts[documentIndex].Match.NewRoot, PreservationMode.PreserveIdentity); + documentIndex++; + } + + newProject = newSolution.Projects.Single(); + } + + private static void AssertSpansEqual(IEnumerable expected, IEnumerable actual, SourceText newText) { AssertEx.Equal( expected, actual.Select(span => newText.Lines.GetTextSpan(span)), itemSeparator: "\r\n", - itemInspector: s => DisplaySpan(newSource, s)); + itemInspector: s => DisplaySpan(newText, s)); } - private static string DisplaySpan(string source, TextSpan span) - => span + ": [" + source.Substring(span.Start, span.Length).Replace("\r\n", " ") + "]"; + private static string DisplaySpan(SourceText source, TextSpan span) + => span + ": [" + source.GetSubText(span).ToString().Replace("\r\n", " ") + "]"; internal static IEnumerable> GetMethodMatches(AbstractEditAndContinueAnalyzer analyzer, Match bodyMatch) { diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/Extensions.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/Extensions.cs index dda94c6ed5806..797ba244a68da 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/Extensions.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/Extensions.cs @@ -7,39 +7,24 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { internal static class Extensions { - public static void Verify(this IEnumerable diagnostics, string newSource, params RudeEditDiagnosticDescription[] expectedDiagnostics) - => diagnostics.ToDescription(newSource, expectedDiagnostics.Any(d => d.FirstLine != null)).Verify(expectedDiagnostics); - - public static void Verify(this IEnumerable diagnostics, params RudeEditDiagnosticDescription[] expectedDiagnostics) - { - expectedDiagnostics ??= Array.Empty(); - AssertEx.SetEqual(expectedDiagnostics, diagnostics, itemSeparator: ",\r\n"); - } - - public static IEnumerable ToDescription(this IEnumerable diagnostics, string newSource, bool includeFirstLines) + public static IEnumerable ToDescription(this IEnumerable diagnostics, SourceText newSource, bool includeFirstLines) { return diagnostics.Select(d => new RudeEditDiagnosticDescription( d.Kind, - d.Span == default ? null : newSource.Substring(d.Span.Start, d.Span.Length), + d.Span == default ? null : newSource.ToString(d.Span), d.Arguments, - firstLine: includeFirstLines ? GetLineAt(newSource, d.Span.Start) : null)); + firstLine: includeFirstLines ? newSource.Lines.GetLineFromPosition(d.Span.Start).ToString().Trim() : null)); } private const string LineSeparator = "\r\n"; - private static string GetLineAt(string source, int position) - { - var start = source.LastIndexOf(LineSeparator, position, position); - var end = source.IndexOf(LineSeparator, position); - return source.Substring(start + 1, end - start).Trim(); - } - public static IEnumerable ToLines(this string str) { var i = 0; diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs index ab646174bfa5a..840e9377ec21a 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs @@ -13,22 +13,27 @@ public sealed class SemanticEditDescription { public readonly SemanticEditKind Kind; public readonly Func SymbolProvider; - public readonly IEnumerable> SyntaxMap; - public readonly bool PreserveLocalVariables; - public readonly int SyntaxTreeOrdinal; + public readonly Func? PartialType; + + /// + /// If specified the node mappings will be validated against the actual syntax map function. + /// + public readonly IEnumerable>? SyntaxMap; + + public readonly bool HasSyntaxMap; public SemanticEditDescription( SemanticEditKind kind, Func symbolProvider, - IEnumerable> syntaxMap, - bool preserveLocalVariables, - int syntaxTreeOrdinal = 0) + Func? partialType, + IEnumerable>? syntaxMap, + bool hasSyntaxMap) { Kind = kind; SymbolProvider = symbolProvider; SyntaxMap = syntaxMap; - PreserveLocalVariables = preserveLocalVariables; - SyntaxTreeOrdinal = syntaxTreeOrdinal; + PartialType = partialType; + HasSyntaxMap = hasSyntaxMap; } } } diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index 04e4644f3cf9e..cb2eb9e64e92a 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -235,14 +235,18 @@ protected static LSP.CompletionParams CreateCompletionParams( }; protected static async Task CreateCompletionItemAsync( - string insertText, + string label, LSP.CompletionItemKind kind, string[] tags, LSP.CompletionParams request, Document document, bool preselect = false, ImmutableArray? commitCharacters = null, + LSP.TextEdit? textEdit = null, + string? insertText = null, string? sortText = null, + string? filterText = null, + string? displayText = null, int resultId = 0) { var position = await document.GetPositionFromLinePositionAsync( @@ -252,15 +256,16 @@ protected static LSP.CompletionParams CreateCompletionParams( var item = new LSP.VSCompletionItem() { - FilterText = insertText, + TextEdit = textEdit, InsertText = insertText, - Label = insertText, - SortText = sortText ?? insertText, + FilterText = filterText ?? label, + Label = label, + SortText = sortText ?? label, InsertTextFormat = LSP.InsertTextFormat.Plaintext, Kind = kind, Data = JObject.FromObject(new CompletionResolveData() { - DisplayText = insertText, + DisplayText = displayText ?? label, TextDocument = request.TextDocument, Position = request.Position, CompletionTrigger = completionTrigger, @@ -278,6 +283,17 @@ protected static LSP.CompletionParams CreateCompletionParams( return item; } + protected static LSP.TextEdit GenerateTextEdit(string newText, int startLine, int startChar, int endLine, int endChar) + => new LSP.TextEdit + { + NewText = newText, + Range = new LSP.Range + { + Start = new LSP.Position { Line = startLine, Character = startChar }, + End = new LSP.Position { Line = endLine, Character = endChar } + } + }; + private protected static CodeActionResolveData CreateCodeActionResolveData(string uniqueIdentifier, LSP.Location location) => new CodeActionResolveData(uniqueIdentifier, location.Range, CreateTextDocumentIdentifier(location.Uri)); diff --git a/src/EditorFeatures/Text/Extensions.cs b/src/EditorFeatures/Text/Extensions.cs index c38c3d7ab7c61..c0abe4ace4d07 100644 --- a/src/EditorFeatures/Text/Extensions.cs +++ b/src/EditorFeatures/Text/Extensions.cs @@ -84,19 +84,6 @@ public static IEnumerable GetRelatedDocumentsWithChanges(this ITextSna public static IEnumerable GetRelatedDocuments(this ITextBuffer buffer) => buffer.AsTextContainer().GetRelatedDocuments(); - /// - /// Tries to get the document corresponding to the text from the current partial solution - /// associated with the text's container. If the document does not contain the exact text a document - /// from a new solution containing the specified text is constructed. If no document is associated - /// with the specified text's container, or the text's container isn't associated with a workspace, - /// then the method returns false. - /// - internal static Document? GetDocumentWithFrozenPartialSemantics(this SourceText text, CancellationToken cancellationToken) - { - var document = text.GetOpenDocumentInCurrentContextWithChanges(); - return document?.WithFrozenPartialSemantics(cancellationToken); - } - internal static bool CanApplyChangeDocumentToWorkspace(this ITextBuffer buffer) => Workspace.TryGetWorkspace(buffer.AsTextContainer(), out var workspace) && workspace.CanApplyChange(ApplyChangesKind.ChangeDocument); diff --git a/src/EditorFeatures/VisualBasicTest/CommentSelection/VisualBasicToggleLineCommentTests.vb b/src/EditorFeatures/VisualBasicTest/CommentSelection/VisualBasicToggleLineCommentTests.vb index 93449969092b7..22eddcdf992c0 100644 --- a/src/EditorFeatures/VisualBasicTest/CommentSelection/VisualBasicToggleLineCommentTests.vb +++ b/src/EditorFeatures/VisualBasicTest/CommentSelection/VisualBasicToggleLineCommentTests.vb @@ -27,7 +27,7 @@ End Class Dim expected = Class A -[| 'Function M() + [|'Function M() ' Dim a = 1 'End Function|] @@ -51,7 +51,7 @@ End Class Dim expected = Class A -[| Function M() + [|Function M() Dim a = 1 End Function|] @@ -76,7 +76,7 @@ End Class { Class A -[| 'Function M() + [|'Function M() ' Dim a = 1 'End Function|] @@ -84,7 +84,7 @@ End Class .Value, Class A -[| Function M() + [|Function M() Dim a = 1 End Function|] diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.vb index c9cf512757760..93a087195ce35 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/ExtensionMethodImportCompletionProviderTests.vb @@ -18,12 +18,14 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet Private Property TimeoutInMilliseconds As Integer = -1 Private Property ShowImportCompletionItemsOptionValue As Boolean = True + Private Property UsePartialSemantic As Boolean = False Protected Overrides Function WithChangedOptions(options As OptionSet) As OptionSet Return options _ .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic, ShowImportCompletionItemsOptionValue) _ .WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion) _ - .WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForExtensionMethodImportCompletion, TimeoutInMilliseconds) + .WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForExtensionMethodImportCompletion, TimeoutInMilliseconds) _ + .WithChangedOption(CompletionServiceOptions.UsePartialSemanticForImportCompletion, UsePartialSemantic) End Function Protected Overrides Function GetComposition() As TestComposition diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.vb index 890b260696255..a21a9f8bafc6a 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/TypeImportCompletionProviderTests.vb @@ -15,11 +15,13 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet Private Property ShowImportCompletionItemsOptionValue As Boolean = True Private Property IsExpandedCompletion As Boolean = True + Private Property UsePartialSemantic As Boolean = False Protected Overrides Function WithChangedOptions(options As OptionSet) As OptionSet Return options _ .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic, ShowImportCompletionItemsOptionValue) _ - .WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion) + .WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion) _ + .WithChangedOption(CompletionServiceOptions.UsePartialSemanticForImportCompletion, UsePartialSemantic) End Function Protected Overrides Function GetComposition() As TestComposition diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.vb index 9f5670d598ebc..d5248932fdd83 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/MakeMethodAsynchronous/MakeMethodAsynchronousTests.vb @@ -232,17 +232,23 @@ Async Function rtrt() As Task(Of Integer) Public Async Function TestBadAwaitInNonAsyncFunction3() As Task Dim initial = - + +Module M1 Function rtrt() As Integer [|Await Nothing|] End Function - +End Module + Dim expected = - -Async Function rtrtAsync() As Threading.Tasks.Task(Of Integer) - Await Nothing + +Imports System.Threading.Tasks + +Module M1 + Async Function rtrtAsync() As Task(Of Integer) + Await Nothing End Function - +End Module + Await TestAsync(initial, expected) End Function @@ -302,8 +308,10 @@ End Class Dim expected = +Imports System.Threading.Tasks + Class Program - Async Function rtrtAsync() As System.Threading.Tasks.Task(Of Integer) + Async Function rtrtAsync() As Task(Of Integer) Await Nothing End Function End Class @@ -323,8 +331,10 @@ End Class Dim expected = +Imports System.Threading.Tasks + Class Program - Async Function rtrtAsync() As System.Threading.Tasks.Task(Of Program) + Async Function rtrtAsync() As Task(Of Program) Await Nothing End Function End Class diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index 529323c16a599..ab29de1e29ad4 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -6,6 +6,7 @@ Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests + Public Class ActiveStatementTests Inherits EditingTestBase @@ -263,7 +264,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.method)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "Goo(Integer)"))) End Sub @@ -283,7 +284,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, Nothing, FeaturesResources.class_)) + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.class_, "C"))) End Sub @@ -316,7 +317,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Shared Sub Main()")) + Diagnostic(RudeEditKind.DeleteActiveStatement, "Shared Sub Main()", FeaturesResources.code)) End Sub @@ -421,21 +422,21 @@ End Class Dim edits = GetTopEdits(src1, src2) Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Do"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "If True"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "Else"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "Case 1, 2"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "Case Else"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "While True"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "Do Until True"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "If True Then"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "Else"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "For i = 0 To 10"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "For Each i In {1, 2}"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "Using z = New C()"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "With expr"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "Shared Sub Main()"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "SyncLock Nothing")) + Diagnostic(RudeEditKind.DeleteActiveStatement, "Do", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "If True", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Else", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Case 1, 2", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Case Else", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "While True", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Do Until True", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "If True Then", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Else", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "For i = 0 To 10", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "For Each i In {1, 2}", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Using z = New C()", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "With expr", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Shared Sub Main()", FeaturesResources.code), + Diagnostic(RudeEditKind.DeleteActiveStatement, "SyncLock Nothing", FeaturesResources.code)) End Sub @@ -476,7 +477,7 @@ End Class Dim edits = GetTopEdits(src1, src2) Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "If c1 Then")) + Diagnostic(RudeEditKind.DeleteActiveStatement, "If c1 Then", FeaturesResources.code)) End Sub @@ -513,7 +514,7 @@ End Class Dim edits = GetTopEdits(src1, src2) Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "If c1 Then")) + Diagnostic(RudeEditKind.DeleteActiveStatement, "If c1 Then", FeaturesResources.code)) End Sub @@ -648,7 +649,7 @@ End Class Dim edits = GetTopEdits(src1, src2) Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Sub Main()")) + Diagnostic(RudeEditKind.DeleteActiveStatement, "Sub Main()", FeaturesResources.code)) End Sub @@ -696,7 +697,7 @@ End Module Dim edits = GetTopEdits(src1, src2) Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, Nothing, "module")) + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(VBFeaturesResources.module_, "Module1"))) End Sub #End Region @@ -1652,8 +1653,8 @@ End Class edits.VerifyRudeDiagnostics(active, Diagnostic(RudeEditKind.ActiveStatementUpdate, "c As New D(2)"), Diagnostic(RudeEditKind.ActiveStatementUpdate, "e As New D(2)"), - Diagnostic(RudeEditKind.Delete, "a As New D(2)", FeaturesResources.field), - Diagnostic(RudeEditKind.Delete, "e As New D(2)", FeaturesResources.field)) + Diagnostic(RudeEditKind.Delete, "a As New D(2)", DeletedSymbolDisplay(FeaturesResources.field, "b")), + Diagnostic(RudeEditKind.Delete, "e As New D(2)", DeletedSymbolDisplay(FeaturesResources.field, "f"))) End Sub @@ -1775,12 +1776,12 @@ End Class Public Sub Initializer_Array_Update3() Dim src1 = " Class C - Private a(1) - Private e(1) - Private f(1) + Private a(1,0) + Private e(1,0) + Private f(1,0) Sub Main - Dim c(1) + Dim c(1,0) End Sub End Class " @@ -1800,11 +1801,8 @@ End Class Dim edits = GetTopEdits(src1, src2) Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ActiveStatementUpdate, "c(1,2)"), - Diagnostic(RudeEditKind.TypeUpdate, "a(1,2)", FeaturesResources.field), Diagnostic(RudeEditKind.ActiveStatementUpdate, "e(1,2)"), - Diagnostic(RudeEditKind.TypeUpdate, "e(1,2)", FeaturesResources.field), - Diagnostic(RudeEditKind.TypeUpdate, "f(1,2)", FeaturesResources.field)) + Diagnostic(RudeEditKind.ActiveStatementUpdate, "c(1,2)")) End Sub @@ -2091,7 +2089,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.field)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.field, "a"))) End Sub @@ -2144,7 +2142,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "a, c As New D()", FeaturesResources.field)) + Diagnostic(RudeEditKind.Delete, "a, c As New D()", DeletedSymbolDisplay(FeaturesResources.field, "b"))) End Sub @@ -2171,7 +2169,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "a, b As New D()", FeaturesResources.field)) + Diagnostic(RudeEditKind.Delete, "a, b As New D()", DeletedSymbolDisplay(FeaturesResources.field, "c"))) End Sub @@ -2225,7 +2223,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "a,b As Integer", FeaturesResources.field)) + Diagnostic(RudeEditKind.Delete, "a,b As Integer", DeletedSymbolDisplay(FeaturesResources.field, "c"))) End Sub @@ -2328,7 +2326,7 @@ End Class Class C Dim a As Integer = 1 Shared b As Integer = 1 - Dim c(1) As Integer = 1 + Dim c(1) As Integer End Class " @@ -2859,7 +2857,7 @@ Class Test For Each b In e1 For Each c In e1 Dim a = Sub() - For Each a In e1 + For Each z In e1 System.Console.Write() Next End Sub @@ -2874,7 +2872,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.InsertAroundActiveStatement, "For Each a In e1", VBFeaturesResources.For_Each_block), + Diagnostic(RudeEditKind.InsertAroundActiveStatement, "For Each z In e1", VBFeaturesResources.For_Each_block), Diagnostic(RudeEditKind.InsertAroundActiveStatement, "For Each b In e1", VBFeaturesResources.For_Each_block)) End Sub @@ -3497,7 +3495,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Try")) + Diagnostic(RudeEditKind.DeleteActiveStatement, "Try", FeaturesResources.code)) End Sub @@ -3561,7 +3559,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Try")) + Diagnostic(RudeEditKind.DeleteActiveStatement, "Try", FeaturesResources.code)) End Sub @@ -5294,7 +5292,7 @@ Imports System Imports System.Threading.Tasks Class C Sub F() - Dim f = Function(a) Function(b) a + b + Dim f = Function(a) Task.FromResult(Function(b) 1) End Sub End Class " @@ -5303,7 +5301,7 @@ Imports System Imports System.Threading.Tasks Class C Sub F() - Dim f = Async Function(a) Function(b) a + b + Dim f = Async Function(a) Function(b) 1 End Sub End Class " @@ -5322,7 +5320,7 @@ Imports System Imports System.Threading.Tasks Class C Sub F() - Dim f = Function(a) Function(b) a + b + Dim f = Function(a) Task.FromResult(Function(b) a + b) End Sub End Class " @@ -5362,7 +5360,7 @@ Class C End Function Sub F() - Dim f = Iterator Function() + Dim f = Iterator Function() As IEnumerable(Of Integer) G() End Function End Sub @@ -5372,14 +5370,14 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "Iterator Function()")) + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "Iterator Function() As IEnumerable(Of Integer)")) End Sub Public Sub AsyncLambdaToLambda_WithoutActiveStatement_NoAwait() Dim src1 = " Class C - Function G() As Task(Of Integer) + Shared Function G() As Task(Of Integer) Return Nothing End Function @@ -5391,7 +5389,7 @@ End Class " Dim src2 = " Class C - Function G() As Task(Of Integer) + Shared Function G() As Task(Of Integer) Return Nothing End Function @@ -5415,7 +5413,7 @@ End Class Imports System Imports System.Threading.Tasks Class C - Function G() As IEnumerable(Of Integer) + Shared Function G() As IEnumerable(Of Integer) Return Nothing End Function @@ -5429,7 +5427,7 @@ End Class Imports System Imports System.Threading.Tasks Class C - Function G() As IEnumerable(Of Integer) + Shared Function G() As IEnumerable(Of Integer) Return Nothing End Function @@ -5715,7 +5713,7 @@ End Class active.OldStatements(0) = active.OldStatements(0).WithFlags(ActiveStatementFlags.PartiallyExecuted Or ActiveStatementFlags.IsLeafFrame) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.PartiallyExecutedActiveStatementDelete, "Sub F()")) + Diagnostic(RudeEditKind.PartiallyExecutedActiveStatementDelete, "Sub F()", FeaturesResources.code)) End Sub @@ -5739,7 +5737,7 @@ End Class active.OldStatements(0) = active.OldStatements(0).WithFlags(ActiveStatementFlags.IsNonLeafFrame Or ActiveStatementFlags.IsLeafFrame) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Sub F()")) + Diagnostic(RudeEditKind.DeleteActiveStatement, "Sub F()", FeaturesResources.code)) End Sub End Class End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTrackingServiceTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTrackingServiceTests.vb index 585d77ec92c71..a3d85ad5e6c28 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTrackingServiceTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTrackingServiceTests.vb @@ -3,6 +3,7 @@ ' See the LICENSE file in the project root for more information. Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests + Public Class ActiveStatementTrackingServiceTests Inherits EditingTestBase diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb index 094e3b79bf762..a056621841930 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb @@ -17,7 +17,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests source As String, description As ActiveStatementsDescription) - VisualBasicEditAndContinueTestHelpers.CreateInstance().VerifyUnchangedDocument( + Dim validator = New VisualBasicEditAndContinueTestHelpers() + validator.VerifyUnchangedDocument( ActiveStatementsDescription.ClearTags(source), description.OldStatements, description.NewSpans, @@ -34,7 +35,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Friend Sub VerifyRudeDiagnostics(editScript As EditScript(Of SyntaxNode), description As ActiveStatementsDescription, ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VisualBasicEditAndContinueTestHelpers.CreateInstance().VerifyRudeDiagnostics(editScript, description, expectedDiagnostics) + VerifySemantics(editScript, description, diagnostics:=expectedDiagnostics) End Sub @@ -42,33 +43,48 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests expectedLineEdits As IEnumerable(Of SourceLineUpdate), expectedNodeUpdates As IEnumerable(Of String), ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VisualBasicEditAndContinueTestHelpers.CreateInstance().VerifyLineEdits(editScript, expectedLineEdits, expectedNodeUpdates, expectedDiagnostics) + Dim validator = New VisualBasicEditAndContinueTestHelpers() + validator.VerifyLineEdits(editScript, expectedLineEdits, expectedNodeUpdates, expectedDiagnostics) End Sub Friend Sub VerifySemanticDiagnostics(editScript As EditScript(Of SyntaxNode), ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics({editScript}, ActiveStatementsDescription.Empty, Nothing, expectedDiagnostics) + VerifySemantics( + {editScript}, + {New DocumentAnalysisResultsDescription(diagnostics:=expectedDiagnostics)}) + End Sub + + + Friend Sub VerifySemanticDiagnostics(editScript As EditScript(Of SyntaxNode), + targetFrameworks As TargetFramework(), + ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) + VerifySemantics( + {editScript}, + {New DocumentAnalysisResultsDescription(diagnostics:=expectedDiagnostics)}, + targetFrameworks:=targetFrameworks) End Sub Friend Sub VerifySemantics(editScript As EditScript(Of SyntaxNode), - activeStatements As ActiveStatementsDescription, - expectedSemanticEdits As SemanticEditDescription(), - ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics({editScript}, activeStatements, expectedSemanticEdits, expectedDiagnostics) + Optional activeStatements As ActiveStatementsDescription = Nothing, + Optional semanticEdits As SemanticEditDescription() = Nothing, + Optional diagnostics As RudeEditDiagnosticDescription() = Nothing, + Optional targetFrameworks As TargetFramework() = Nothing) + VerifySemantics( + {editScript}, + {New DocumentAnalysisResultsDescription(activeStatements, semanticEdits, diagnostics)}, + targetFrameworks) End Sub Friend Sub VerifySemantics(editScripts As EditScript(Of SyntaxNode)(), - Optional activeStatements As ActiveStatementsDescription = Nothing, - Optional expectedSemanticEdits As SemanticEditDescription() = Nothing, - Optional expectedDiagnostics As RudeEditDiagnosticDescription() = Nothing) - VisualBasicEditAndContinueTestHelpers.CreateInstance().VerifySemantics( - editScripts, - activeStatements, - expectedSemanticEdits, - expectedDiagnostics) + expected As DocumentAnalysisResultsDescription(), + Optional targetFrameworks As TargetFramework() = Nothing) + For Each framework In If(targetFrameworks, {TargetFramework.NetStandard20, TargetFramework.NetCoreApp}) + Dim validator = New VisualBasicEditAndContinueTestHelpers() + validator.VerifySemantics(editScripts, framework, expected) + Next End Sub End Module End Namespace diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb index 306bf1acadef7..e22a1a8c89dc8 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb @@ -9,6 +9,7 @@ Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EditAndContinue Imports Microsoft.CodeAnalysis.Emit Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests @@ -20,27 +21,56 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Return New VisualBasicEditAndContinueAnalyzer() End Function - Public Enum StateMachineKind - None + Public Enum MethodKind + Regular Async Iterator End Enum + Friend Shared NoSemanticEdits As SemanticEditDescription() = Array.Empty(Of SemanticEditDescription) + Friend Overloads Shared Function Diagnostic(rudeEditKind As RudeEditKind, squiggle As String, ParamArray arguments As String()) As RudeEditDiagnosticDescription Return New RudeEditDiagnosticDescription(rudeEditKind, squiggle, arguments, firstLine:=Nothing) End Function - Friend Shared Function SemanticEdit(kind As SemanticEditKind, symbolProvider As Func(Of Compilation, ISymbol), syntaxMap As IEnumerable(Of KeyValuePair(Of TextSpan, TextSpan))) As SemanticEditDescription - Assert.NotNull(syntaxMap) - Return New SemanticEditDescription(kind, symbolProvider, syntaxMap, preserveLocalVariables:=True) + Friend Shared Function SemanticEdit(kind As SemanticEditKind, + symbolProvider As Func(Of Compilation, ISymbol), + syntaxMap As IEnumerable(Of KeyValuePair(Of TextSpan, TextSpan)), + Optional partialType As String = Nothing) As SemanticEditDescription + Return New SemanticEditDescription( + kind, + symbolProvider, + If(partialType Is Nothing, Nothing, Function(c As Compilation) CType(c.GetMember(partialType), ITypeSymbol)), + syntaxMap:=Nothing, + hasSyntaxMap:=syntaxMap IsNot Nothing) + End Function + + Friend Shared Function SemanticEdit(kind As SemanticEditKind, + symbolProvider As Func(Of Compilation, ISymbol), + Optional partialType As String = Nothing, + Optional preserveLocalVariables As Boolean = False) As SemanticEditDescription + Return New SemanticEditDescription( + kind, + symbolProvider, + If(partialType Is Nothing, Nothing, Function(c As Compilation) CType(c.GetMember(partialType), ITypeSymbol)), + syntaxMap:=Nothing, + hasSyntaxMap:=preserveLocalVariables) + End Function + + Friend Shared Function DeletedSymbolDisplay(kind As String, displayName As String) As String + Return String.Format(FeaturesResources.member_kind_and_name, kind, displayName) End Function - Friend Shared Function SemanticEdit(kind As SemanticEditKind, symbolProvider As Func(Of Compilation, ISymbol), Optional preserveLocalVariables As Boolean = False) As SemanticEditDescription - Return New SemanticEditDescription(kind, symbolProvider, Nothing, preserveLocalVariables) + Friend Shared Function DocumentResults( + Optional activeStatements As ActiveStatementsDescription = Nothing, + Optional semanticEdits As SemanticEditDescription() = Nothing, + Optional diagnostics As RudeEditDiagnosticDescription() = Nothing) As DocumentAnalysisResultsDescription + Return New DocumentAnalysisResultsDescription(activeStatements, semanticEdits, diagnostics) End Function Private Shared Function ParseSource(source As String) As SyntaxTree - Return VisualBasicEditAndContinueTestHelpers.CreateInstance().ParseText(ActiveStatementsDescription.ClearTags(source)) + Dim validator = New VisualBasicEditAndContinueTestHelpers() + Return validator.ParseText(ActiveStatementsDescription.ClearTags(source)) End Function Friend Shared Function GetTopEdits(src1 As String, src2 As String) As EditScript(Of SyntaxNode) @@ -54,14 +84,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Return match.GetTreeEdits() End Function - Friend Shared Function GetMethodEdits(src1 As String, src2 As String, Optional stateMachine As StateMachineKind = StateMachineKind.None) As EditScript(Of SyntaxNode) - Dim match = GetMethodMatch(src1, src2, stateMachine) + Public Shared Function GetTopEdits(methodEdits As EditScript(Of SyntaxNode)) As EditScript(Of SyntaxNode) + Dim oldMethodSource = methodEdits.Match.OldRoot.ToFullString() + Dim newMethodSource = methodEdits.Match.NewRoot.ToFullString() + + Return GetTopEdits(WrapMethodBodyWithClass(oldMethodSource), WrapMethodBodyWithClass(newMethodSource)) + End Function + + Friend Shared Function GetMethodEdits(src1 As String, src2 As String, Optional methodKind As MethodKind = MethodKind.Regular) As EditScript(Of SyntaxNode) + Dim match = GetMethodMatch(src1, src2, methodKind) Return match.GetTreeEdits() End Function - Friend Shared Function GetMethodMatch(src1 As String, src2 As String, Optional stateMachine As StateMachineKind = StateMachineKind.None) As Match(Of SyntaxNode) - Dim m1 = MakeMethodBody(src1, stateMachine) - Dim m2 = MakeMethodBody(src2, stateMachine) + Friend Shared Function GetMethodMatch(src1 As String, src2 As String, Optional methodKind As MethodKind = MethodKind.Regular) As Match(Of SyntaxNode) + Dim m1 = MakeMethodBody(src1, methodKind) + Dim m2 = MakeMethodBody(src2, methodKind) Dim diagnostics = New ArrayBuilder(Of RudeEditDiagnostic)() @@ -69,9 +106,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Dim match = CreateAnalyzer().GetTestAccessor().ComputeBodyMatch(m1, m2, Array.Empty(Of AbstractEditAndContinueAnalyzer.ActiveNode)(), diagnostics, oldHasStateMachineSuspensionPoint, newHasStateMachineSuspensionPoint) Dim needsSyntaxMap = oldHasStateMachineSuspensionPoint AndAlso newHasStateMachineSuspensionPoint - Assert.Equal(stateMachine <> StateMachineKind.None, needsSyntaxMap) + Assert.Equal(methodKind <> MethodKind.Regular, needsSyntaxMap) - If stateMachine = StateMachineKind.None Then + If methodKind = MethodKind.Regular Then Assert.Empty(diagnostics) End If @@ -80,7 +117,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Public Shared Function GetMethodMatches(src1 As String, src2 As String, - Optional stateMachine As StateMachineKind = StateMachineKind.None) As IEnumerable(Of KeyValuePair(Of SyntaxNode, SyntaxNode)) + Optional stateMachine As MethodKind = MethodKind.Regular) As IEnumerable(Of KeyValuePair(Of SyntaxNode, SyntaxNode)) Dim methodMatch = GetMethodMatch(src1, src2, stateMachine) Return EditAndContinueTestHelpers.GetMethodMatches(CreateAnalyzer(), methodMatch) End Function @@ -93,18 +130,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Return EditAndContinueTestHelpers.ToMatchingPairs(matches) End Function - Friend Shared Function MakeMethodBody(bodySource As String, Optional stateMachine As StateMachineKind = StateMachineKind.None) As SyntaxNode - Dim source As String - Select Case stateMachine - Case StateMachineKind.Iterator - source = "Class C" & vbLf & "Iterator Function F() As IEnumerable(Of Integer)" & vbLf & bodySource & " : End Function : End Class" - - Case StateMachineKind.Async - source = "Class C" & vbLf & "Async Function F() As Task(Of Integer)" & vbLf & bodySource & " : End Function : End Class" - - Case Else - source = "Class C" & vbLf & "Sub F()" & vbLf & bodySource & " : End Sub : End Class" - End Select + Friend Shared Function MakeMethodBody(bodySource As String, Optional stateMachine As MethodKind = MethodKind.Regular) As SyntaxNode + Dim source = WrapMethodBodyWithClass(bodySource, stateMachine) Dim tree = ParseSource(source) Dim root = tree.GetRoot() @@ -112,7 +139,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Dim declaration = DirectCast(DirectCast(root, CompilationUnitSyntax).Members(0), ClassBlockSyntax).Members(0) Return SyntaxFactory.SyntaxTree(declaration).GetRoot() + End Function + Private Shared Function WrapMethodBodyWithClass(bodySource As String, Optional kind As MethodKind = MethodKind.Regular) As String + Select Case kind + Case MethodKind.Iterator + Return "Class C" & vbLf & "Iterator Function F() As IEnumerable(Of Integer)" & vbLf & bodySource & " : End Function : End Class" + + Case MethodKind.Async + Return "Class C" & vbLf & "Async Function F() As Task(Of Integer)" & vbLf & bodySource & " : End Function : End Class" + + Case Else + Return "Class C" & vbLf & "Sub F()" & vbLf & bodySource & " : End Sub : End Class" + End Select End Function Friend Shared Function GetActiveStatements(oldSource As String, newSource As String) As ActiveStatementsDescription diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb index 354f4f122e4b4..a8efa33be4acf 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/VisualBasicEditAndContinueTestHelpers.vb @@ -8,33 +8,17 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.CodeAnalysis.EditAndContinue.UnitTests Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.Differencing Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EditAndContinue Friend NotInheritable Class VisualBasicEditAndContinueTestHelpers Inherits EditAndContinueTestHelpers - Private ReadOnly _analyzer As VisualBasicEditAndContinueAnalyzer = New VisualBasicEditAndContinueAnalyzer() + Private ReadOnly _analyzer As VisualBasicEditAndContinueAnalyzer - Private ReadOnly _fxReferences As ImmutableArray(Of PortableExecutableReference) - - Friend Shared Function CreateInstance() As VisualBasicEditAndContinueTestHelpers - Return New VisualBasicEditAndContinueTestHelpers( - ImmutableArray.Create(TestMetadata.Net451.mscorlib, TestMetadata.Net451.System, TestMetadata.Net451.SystemCore)) - End Function - - Friend Shared Function CreateInstance40() As VisualBasicEditAndContinueTestHelpers - Return New VisualBasicEditAndContinueTestHelpers( - ImmutableArray.Create(TestMetadata.Net40.mscorlib, TestMetadata.Net40.SystemCore)) - End Function - - Friend Shared Function CreateInstanceMinAsync() As VisualBasicEditAndContinueTestHelpers - Return New VisualBasicEditAndContinueTestHelpers( - ImmutableArray.Create(TestReferences.NetFx.Minimal.mincorlib, TestReferences.NetFx.Minimal.minasync)) - End Function - - Public Sub New(fxReferences As ImmutableArray(Of PortableExecutableReference)) - _fxReferences = fxReferences + Public Sub New(Optional faultInjector As Action(Of SyntaxNode) = Nothing) + _analyzer = New VisualBasicEditAndContinueAnalyzer(faultInjector) End Sub Public Overrides ReadOnly Property Analyzer As AbstractEditAndContinueAnalyzer @@ -43,12 +27,17 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EditAndContinue End Get End Property - Public Overrides Function CreateLibraryCompilation(name As String, trees As IEnumerable(Of SyntaxTree)) As Compilation - Return VisualBasicCompilation.Create("New", - trees, - _fxReferences, - TestOptions.ReleaseDll.WithEmbedVbCoreRuntime(True)) - End Function + Public Overrides ReadOnly Property LanguageName As String + Get + Return LanguageNames.VisualBasic + End Get + End Property + + Public Overrides ReadOnly Property TopSyntaxComparer As TreeComparer(Of SyntaxNode) + Get + Return CodeAnalysis.VisualBasic.EditAndContinue.TopSyntaxComparer.Instance + End Get + End Property Public Overrides Function ParseText(source As String) As SyntaxTree Return SyntaxFactory.ParseSyntaxTree(source, VisualBasicParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest)) diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb index 70c364944d9ec..b97b5bc74b75d 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb @@ -8,6 +8,7 @@ Imports Microsoft.CodeAnalysis.Emit Imports Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests + Public Class LineEditTests Inherits EditingTestBase diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb index ac7c736e51c27..adcca4c678123 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb @@ -10,7 +10,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests - + Public Class StatementEditingTests Inherits EditingTestBase @@ -506,7 +506,7 @@ Do : Dim a = 14, b = 15, c = 16 : Console.WriteLine(a + b + c) : Loop Dim src1 = "Dim result = From a In {Await F(0)}, b In {Q(Async Function() Await F(2))} Select a + b" & vbLf Dim src2 = "Dim result = From a In {Await F(1)}, b In {Q(Async Function() Await F(3))} Select a - b" & vbLf - Dim match = GetMethodMatch(src1, src2, stateMachine:=StateMachineKind.Async) + Dim match = GetMethodMatch(src1, src2, methodKind:=MethodKind.Async) Dim actual = ToMatchingPairs(match) ' Note that @@ -1261,7 +1261,7 @@ For Each x In {1, 2, 3} Yield 2 Next .Value - Dim match = GetMethodMatches(src1, src2, stateMachine:=StateMachineKind.Iterator) + Dim match = GetMethodMatches(src1, src2, stateMachine:=MethodKind.Iterator) Dim actual = ToMatchingPairs(match) Dim expected = New MatchingPairs From @@ -1300,7 +1300,7 @@ Catch e As Exception When filter(e) End Try " - Dim match = GetMethodMatches(src1, src2, stateMachine:=StateMachineKind.None) + Dim match = GetMethodMatches(src1, src2, stateMachine:=MethodKind.Regular) Dim actual = ToMatchingPairs(match) Dim expected = New MatchingPairs From @@ -4533,7 +4533,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemantics( ActiveStatementsDescription.Empty, - expectedSemanticEdits:= + semanticEdits:= { SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMembers("F").Single(), preserveLocalVariables:=True) }) @@ -5975,7 +5975,7 @@ Yield 2 Yield 3 Yield 4 .Value - Dim edits = GetMethodEdits(src1, src2, stateMachine:=StateMachineKind.Iterator) + Dim edits = GetMethodEdits(src1, src2, methodKind:=MethodKind.Iterator) edits.VerifyEdits( "Update [Yield 1]@50 -> [Yield 3]@50", @@ -5993,7 +5993,7 @@ Yield 2 Yield 3 Yield 4 .Value - Dim edits = GetMethodEdits(src1, src2, stateMachine:=StateMachineKind.Iterator) + Dim edits = GetMethodEdits(src1, src2, methodKind:=MethodKind.Iterator) edits.VerifyEdits( "Update [Yield 1]@50 -> [Yield 3]@50", @@ -6011,7 +6011,7 @@ Yield 1 Yield 2 Yield 3 .Value - Dim edits = GetMethodEdits(src1, src2, stateMachine:=StateMachineKind.Iterator) + Dim edits = GetMethodEdits(src1, src2, methodKind:=MethodKind.Iterator) edits.VerifyEdits( "Insert [Yield 3]@66") @@ -6028,7 +6028,7 @@ Yield 3 Yield 1 Yield 2 .Value - Dim edits = GetMethodEdits(src1, src2, stateMachine:=StateMachineKind.Iterator) + Dim edits = GetMethodEdits(src1, src2, methodKind:=MethodKind.Iterator) edits.VerifyEdits( "Delete [Yield 3]@66") @@ -6055,11 +6055,10 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - VisualBasicEditAndContinueTestHelpers.CreateInstance40().VerifySemantics( - editScripts:={edits}, - activeStatements:=ActiveStatementsDescription.Empty, - expectedSemanticEdits:=Nothing, - expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Iterator Function F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute")}) + VerifySemantics( + edits, + diagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Iterator Function F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute")}, + targetFrameworks:={TargetFramework.Mscorlib40AndSystemCore}) End Sub @@ -6083,11 +6082,9 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - VisualBasicEditAndContinueTestHelpers.CreateInstance40().VerifySemantics( - editScripts:={edits}, - activeStatements:=ActiveStatementsDescription.Empty, - expectedSemanticEdits:=Nothing, - expectedDiagnostics:=Nothing) + VerifySemanticDiagnostics( + editScript:=edits, + targetFrameworks:={TargetFramework.Mscorlib40AndSystemCore}) End Sub #End Region @@ -6103,7 +6100,7 @@ Await F(2) Await F(3) Await F(4) .Value - Dim edits = GetMethodEdits(src1, src2, stateMachine:=StateMachineKind.Async) + Dim edits = GetMethodEdits(src1, src2, methodKind:=MethodKind.Async) edits.VerifyEdits( "Update [Await F(1)]@40 -> [Await F(3)]@40", @@ -6121,7 +6118,7 @@ Await F(1) Await F(2) Await F(3) .Value - Dim edits = GetMethodEdits(src1, src2, stateMachine:=StateMachineKind.Async) + Dim edits = GetMethodEdits(src1, src2, methodKind:=MethodKind.Async) edits.VerifyEdits( "Insert [Await F(2)]@51") @@ -6138,7 +6135,7 @@ Await F(3) Await F(1) Await F(3) .Value - Dim edits = GetMethodEdits(src1, src2, stateMachine:=StateMachineKind.Async) + Dim edits = GetMethodEdits(src1, src2, methodKind:=MethodKind.Async) edits.VerifyEdits( "Delete [Await F(2)]@51") @@ -6167,11 +6164,11 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - VisualBasicEditAndContinueTestHelpers.CreateInstanceMinAsync().VerifySemantics( - editScripts:={edits}, - activeStatements:=ActiveStatementsDescription.Empty, - expectedSemanticEdits:=Nothing, - expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Async Function F()", "System.Runtime.CompilerServices.AsyncStateMachineAttribute")}) + + VerifySemantics( + edits, + diagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Async Function F()", "System.Runtime.CompilerServices.AsyncStateMachineAttribute")}, + targetFrameworks:={TargetFramework.MinimalAsync}) End Sub @@ -6196,7 +6193,9 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - VisualBasicEditAndContinueTestHelpers.CreateInstanceMinAsync().VerifySemantics({edits}) + VerifySemanticDiagnostics( + edits, + targetFrameworks:={TargetFramework.MinimalAsync}) End Sub #End Region End Class diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index a0291eec5980f..2dc61abb6f17c 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -9,136 +9,229 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests - Public Class RudeEditTopLevelTests + + Public Class TopLevelEditingTests Inherits EditingTestBase #Region "Imports" Public Sub ImportDelete1() - Dim src1 As String = + Dim src1 = " Imports System.Diagnostics -.Value +" Dim src2 As String = "" + Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits("Delete [Imports System.Diagnostics]@1") + edits.VerifyEdits("Delete [Imports System.Diagnostics]@2") Assert.IsType(Of ImportsStatementSyntax)(edits.Edits.First().OldNode) Assert.Equal(edits.Edits.First().NewNode, Nothing) End Sub Public Sub ImportDelete2() - Dim src1 As String = -Imports System.Diagnostics + Dim src1 = " +Imports D = System.Diagnostics +Imports Imports System.Collections Imports System.Collections.Generic -.Value +" - Dim src2 As String = -Imports System.Diagnostics + Dim src2 = " Imports System.Collections.Generic -.Value +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits("Delete [Imports System.Collections]@28") + edits.VerifyEdits( + "Delete [Imports D = System.Diagnostics]@2", + "Delete [Imports ]@34", + "Delete [Imports System.Collections]@76") + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, Nothing, VBFeaturesResources.import), + Diagnostic(RudeEditKind.Delete, Nothing, VBFeaturesResources.import), Diagnostic(RudeEditKind.Delete, Nothing, VBFeaturesResources.import)) End Sub Public Sub ImportInsert() - Dim src1 As String = -Imports System.Diagnostics + Dim src1 = " Imports System.Collections.Generic -.Value +" - Dim src2 As String = -Imports System.Diagnostics + Dim src2 = " +Imports D = System.Diagnostics +Imports Imports System.Collections Imports System.Collections.Generic -.Value +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits("Insert [Imports System.Collections]@28") + edits.VerifyEdits( + "Insert [Imports D = System.Diagnostics]@2", + "Insert [Imports ]@34", + "Insert [Imports System.Collections]@76") + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "Imports D = System.Diagnostics", VBFeaturesResources.import), + Diagnostic(RudeEditKind.Insert, "Imports ", "import"), Diagnostic(RudeEditKind.Insert, "Imports System.Collections", VBFeaturesResources.import)) End Sub Public Sub ImportUpdate1() - Dim src1 As String = + Dim src1 = " Imports System.Diagnostics Imports System.Collections Imports System.Collections.Generic -.Value +" - Dim src2 As String = + Dim src2 = " Imports System.Diagnostics Imports X = System.Collections Imports System.Collections.Generic -.Value +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits("Update [Imports System.Collections]@28 -> [Imports X = System.Collections]@28") + edits.VerifyEdits( + "Update [Imports System.Collections]@30 -> [Imports X = System.Collections]@30") + edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.Update, "Imports X = System.Collections", VBFeaturesResources.import)) End Sub - + Public Sub ImportUpdate2() - Dim src1 As String = + Dim src1 = " Imports System.Diagnostics Imports X1 = System.Collections +Imports Imports System.Collections.Generic -.Value +" - Dim src2 As String = + Dim src2 = " Imports System.Diagnostics Imports X2 = System.Collections +Imports Imports System.Collections.Generic -.Value +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits("Update [Imports X1 = System.Collections]@28 -> [Imports X2 = System.Collections]@28") + + ' TODO: https://github.com/dotnet/roslyn/issues/51374 + ' Should be following: + 'edits.VerifyEdits( + ' "Update [Imports X1 = System.Collections]@30 -> [Imports X2 = System.Collections]@30", + ' "Update [Imports ]@28 -> [Imports ]@28") + ' + 'edits.VerifyRudeDiagnostics( + ' Diagnostic(RudeEditKind.Update, "Imports X2 = System.Collections", VBFeaturesResources.import), + ' Diagnostic(RudeEditKind.Update, "Imports ", VBFeaturesResources.import)) + + edits.VerifyEdits( + "Update [Imports X1 = System.Collections]@30 -> [Imports X2 = System.Collections]@30") + edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.Update, "Imports X2 = System.Collections", VBFeaturesResources.import)) End Sub Public Sub ImportUpdate3() - Dim src1 As String = + Dim src1 = " Imports System.Diagnostics Imports System.Collections Imports System.Collections.Generic -.Value +" - Dim src2 As String = + Dim src2 = " Imports System Imports System.Collections Imports System.Collections.Generic -.Value +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits("Update [Imports System.Diagnostics]@1 -> [Imports System]@1") + + edits.VerifyEdits( + "Update [Imports System.Diagnostics]@2 -> [Imports System]@2") + edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.Update, "Imports System", VBFeaturesResources.import)) End Sub Public Sub ImportReorder1() - Dim src1 As String = + Dim src1 = " Imports System.Diagnostics Imports System.Collections Imports System.Collections.Generic -.Value +" - Dim src2 As String = + Dim src2 = " Imports System.Collections Imports System.Collections.Generic Imports System.Diagnostics -.Value +" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Reorder [Imports System.Diagnostics]@2 -> @66") + End Sub +#End Region + +#Region "Option" + + Public Sub OptionDelete() + Dim src1 = " +Option Strict On +" + + Dim src2 = " +" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Delete [Option Strict On]@2") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, Nothing, VBFeaturesResources.option_)) + End Sub + + + Public Sub OptionInsert() + Dim src1 = " +" + + Dim src2 = " +Option Strict On +" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Insert [Option Strict On]@2") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "Option Strict On", VBFeaturesResources.option_)) + End Sub + + + Public Sub OptionUpdate() + Dim src1 = " +Option Strict On +" + + Dim src2 = " +Option Strict Off +" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits("Reorder [Imports System.Diagnostics]@1 -> @63") + + edits.VerifyEdits( + "Update [Option Strict On]@2 -> [Option Strict Off]@2") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Update, "Option Strict Off", VBFeaturesResources.option_)) End Sub #End Region @@ -339,7 +432,10 @@ Imports System.Diagnostics Dim src2 = "Partial Interface C : End Interface" Dim edits = GetTopEdits(src1, src2) - edits.VerifyRudeDiagnostics() + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C")) + }) End Sub @@ -348,8 +444,8 @@ Imports System.Diagnostics Dim src2 = "" Dim edits = GetTopEdits(src1, src2) - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, Nothing, FeaturesResources.interface_)) + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.interface_, "C"))) End Sub @@ -379,7 +475,7 @@ Imports System.Diagnostics Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, Nothing, VBFeaturesResources.module_)) + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(VBFeaturesResources.module_, "C"))) End Sub @@ -419,8 +515,7 @@ Imports System.Diagnostics edits.VerifyEdits( "Update [Module C]@0 -> [Partial Module C]@0") - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Partial Module C", VBFeaturesResources.module_)) + edits.VerifyRudeDiagnostics() End Sub @@ -432,8 +527,7 @@ Imports System.Diagnostics edits.VerifyEdits( "Update [Partial Module C]@0 -> [Module C]@0") - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Module C", VBFeaturesResources.module_)) + edits.VerifyRudeDiagnostics() End Sub @@ -471,8 +565,7 @@ Imports System.Diagnostics edits.VerifyEdits( "Update [Interface C]@0 -> [Partial Interface C]@0") - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Partial Interface C", FeaturesResources.interface_)) + edits.VerifyRudeDiagnostics() End Sub @@ -640,361 +733,841 @@ End Interface Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics() End Sub -#End Region -#Region "Enums" - Public Sub Enum_NoModifiers_Insert() - Dim src1 = "" - Dim src2 = "Enum C : End Enum" - Dim edits = GetTopEdits(src1, src2) + Public Sub Interface_InsertMembers() + Dim src1 = " +Imports System - edits.VerifyRudeDiagnostics() - End Sub +Interface I +End Interface +" + Dim src2 = " +Imports System - - Public Sub Enum_NoModifiers_IntoNamespace_Insert() - Dim src1 = "Namespace N : End Namespace" - Dim src2 = "Namespace N : Enum C : End Enum : End Namespace" - Dim edits = GetTopEdits(src1, src2) +Interface I + Sub VirtualMethod() + Property VirtualProperty() As String + Property VirtualIndexer(a As Integer) As String + Event VirtualEvent As Action - edits.VerifyRudeDiagnostics() - End Sub + MustInherit Class C + End Class - - Public Sub Enum_Name_Update() - Dim src1 = "Enum Color : Red = 1 : Blue = 2 : End Enum" - Dim src2 = "Enum Colors : Red = 1 : Blue = 2 : End Enum" - Dim edits = GetTopEdits(src1, src2) + Interface J + End Interface - edits.VerifyEdits( - "Update [Enum Color]@0 -> [Enum Colors]@0") + Enum E + A + End Enum - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Renamed, "Enum Colors", FeaturesResources.enum_)) + Delegate Sub D() +End Interface +" + + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertVirtual, "Sub VirtualMethod()", FeaturesResources.method), + Diagnostic(RudeEditKind.InsertVirtual, "Property VirtualProperty()", FeaturesResources.auto_property), + Diagnostic(RudeEditKind.InsertVirtual, "Property VirtualIndexer(a As Integer)", FeaturesResources.auto_property), + Diagnostic(RudeEditKind.InsertVirtual, "Event VirtualEvent", FeaturesResources.event_)) End Sub - Public Sub Enum_Modifiers_Update() - Dim src1 = "Public Enum Color : Red = 1 : Blue = 2 : End Enum" - Dim src2 = "Enum Color : Red = 1 : Blue = 2 : End Enum" - Dim edits = GetTopEdits(src1, src2) + Public Sub Interface_InsertDelete() + Dim srcA1 = " +Interface I + Sub VirtualMethod() + Function VirtualFunction() As Integer + Property VirtualProperty() As String + ReadOnly Property VirtualReadonlyProperty() As String + Property VirtualIndexer(a As Integer) As String + Event VirtualEvent As Action - edits.VerifyEdits( - "Update [Public Enum Color]@0 -> [Enum Color]@0") + MustInherit Class C + End Class - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Enum Color", FeaturesResources.enum_)) - End Sub + Interface J + End Interface - - Public Sub Enum_BaseType_Insert() - Dim src1 = "Enum Color : Red = 1 : Blue = 2 : End Enum" - Dim src2 = "Enum Color As UShort : Red = 1 : Blue = 2 : End Enum" - Dim edits = GetTopEdits(src1, src2) + Enum E + A + End Enum - edits.VerifyEdits( - "Insert [As UShort]@11") + Delegate Sub D() +End Interface +" + Dim srcB1 = " +" - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "As UShort", VBFeaturesResources.as_clause)) + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) End Sub - Public Sub Enum_BaseType_Update() - Dim src1 = "Enum Color As UShort : Red = 1 : Blue = 2 : End Enum" - Dim src2 = "Enum Color As Long : Red = 1 : Blue = 2 : End Enum" - Dim edits = GetTopEdits(src1, src2) + Public Sub GenericType_InsertMembers() + Dim src1 = " +Imports System +Class C(Of T) +End Class +" + Dim src2 = " +Imports System +Class C(Of T) + Dim F1, F2 As New Object, F3 As Integer, F4 As New Object, F5(1, 2), F6? As Integer - edits.VerifyEdits( - "Update [As UShort]@11 -> [As Long]@11") + Sub M() + End Sub - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeUpdate, "Enum Color", FeaturesResources.enum_)) - End Sub + Property P1(i As Integer) As Integer + Get + Return 1 + End Get + Set(value As Integer) + End Set + End Property - - Public Sub Enum_BaseType_Delete() - Dim src1 = "Enum Color As UShort : Red = 1 : Blue = 2 : End Enum" - Dim src2 = "Enum Color : Red = 1 : Blue = 2 : End Enum" - Dim edits = GetTopEdits(src1, src2) + Property P2 As Integer + Property P3 As New Object - edits.VerifyEdits( - "Delete [As UShort]@11") + Event E1(sender As Object, e As EventArgs) - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Enum Color", VBFeaturesResources.as_clause)) - End Sub + Event E2 As Action - - Public Sub Enum_Attribute_Insert() - Dim src1 = "Enum E : End Enum" - Dim src2 = "Enum E : End Enum" - Dim edits = GetTopEdits(src1, src2) + Custom Event E3 As EventHandler + AddHandler(value As EventHandler) + End AddHandler + RemoveHandler(value As EventHandler) + End RemoveHandler + RaiseEvent(sender As Object, e As EventArgs) + End RaiseEvent + End Event - edits.VerifyEdits( - "Insert []@0", "Insert [A]@1") + Dim WithEvents WE As Object - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "", VBFeaturesResources.attributes)) - End Sub + Enum N + A + End Enum - - Public Sub Enum_MemberAttribute_Delete() - Dim src1 = "Enum E : X : End Enum" - Dim src2 = "Enum E : X : End Enum" + Interface I + End Interface + + Class D + End Class + + Delegate Sub G() +End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Delete []@9", "Delete [A]@10") + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertIntoGenericType, "Sub M()", FeaturesResources.method), + Diagnostic(RudeEditKind.InsertIntoGenericType, "Property P1(i As Integer)", FeaturesResources.property_), + Diagnostic(RudeEditKind.InsertIntoGenericType, "Property P2", FeaturesResources.auto_property), + Diagnostic(RudeEditKind.InsertIntoGenericType, "Property P3", FeaturesResources.auto_property), + Diagnostic(RudeEditKind.InsertIntoGenericType, "Event E1(sender As Object, e As EventArgs)", FeaturesResources.event_), + Diagnostic(RudeEditKind.InsertIntoGenericType, "Event E2", FeaturesResources.event_), + Diagnostic(RudeEditKind.InsertIntoGenericType, "Event E3", FeaturesResources.event_), + Diagnostic(RudeEditKind.InsertIntoGenericType, "F3 As Integer", FeaturesResources.field), + Diagnostic(RudeEditKind.InsertIntoGenericType, "F4 As New Object", FeaturesResources.field), + Diagnostic(RudeEditKind.InsertIntoGenericType, "F1", FeaturesResources.field), + Diagnostic(RudeEditKind.InsertIntoGenericType, "F2", FeaturesResources.field), + Diagnostic(RudeEditKind.InsertIntoGenericType, "F5(1, 2)", FeaturesResources.field), + Diagnostic(RudeEditKind.InsertIntoGenericType, "F6?", FeaturesResources.field), + Diagnostic(RudeEditKind.InsertIntoGenericType, "WE As Object", VBFeaturesResources.WithEvents_field)) + End Sub + + + Public Sub Type_Delete() + Dim src1 = " +Class C + Sub F() + End Sub +End Class - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "X", VBFeaturesResources.attributes)) - End Sub +Module M + Sub F() + End Sub +End Module - - Public Sub Enum_MemberAttribute_Insert() - Dim src1 = "Enum E : X : End Enum" - Dim src2 = "Enum E : X : End Enum" - Dim edits = GetTopEdits(src1, src2) +Structure S + Sub F() + End Sub +End Structure - edits.VerifyEdits( - "Insert []@9", "Insert [A]@10") +Interface I + Sub F() +End Interface - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "", VBFeaturesResources.attributes)) +Delegate Sub D() +" + Dim src2 = "" + + GetTopEdits(src1, src2).VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.class_, "C")), + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(VBFeaturesResources.module_, "M")), + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(VBFeaturesResources.structure_, "S")), + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.interface_, "I")), + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.delegate_, "D"))) End Sub - Public Sub Enum_MemberAttribute_Update() - Dim src1 = "Enum E : X : End Enum" - Dim src2 = "Enum E : X : End Enum" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifyEdits( - "Update [A1]@10 -> [A2]@10") + Public Sub PartialType_Delete() + Dim srcA1 = " +Partial Class C + Sub F() + End Sub + Sub M() + End Sub +End Class" + Dim srcB1 = " +Partial Class C + Sub G() + End Sub +End Class +" + Dim srcA2 = "" + Dim srcB2 = " +Partial Class C + Sub G() + End Sub + Sub M() + End Sub +End Class" - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Update, "A2", FeaturesResources.attribute)) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.method, "C.F()"))}), + DocumentResults( + semanticEdits:={ + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("M")) + }) + }) End Sub - Public Sub Enum_MemberInitializer_Update1() - Dim src1 = "Enum Color : Red = 1 : Blue = 2 : End Enum" - Dim src2 = "Enum Color : Red = 1 : Blue = 3 : End Enum" - - Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Blue = 2]@23 -> [Blue = 3]@23") + Public Sub PartialType_InsertFirstDeclaration() + Dim src1 = "" + Dim src2 = " +Partial Class C + Sub F() + End Sub +End Class" - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.InitializerUpdate, "Blue = 3", FeaturesResources.enum_value)) + GetTopEdits(src1, src2).VerifySemantics( + ActiveStatementsDescription.Empty, + {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C"), preserveLocalVariables:=False)}) End Sub - Public Sub Enum_MemberInitializer_Update2() - Dim src1 = "Enum Color : Red = 1 : Blue = 2 : End Enum" - Dim src2 = "Enum Color : Red = 1 << 0 : Blue = 2 << 1 : End Enum" - Dim edits = GetTopEdits(src1, src2) + Public Sub PartialType_InsertSecondDeclaration() + Dim srcA1 = " +Partial Class C + Sub F() + End Sub +End Class" + Dim srcB1 = "" - edits.VerifyEdits( - "Update [Red = 1]@13 -> [Red = 1 << 0]@13", - "Update [Blue = 2]@23 -> [Blue = 2 << 1]@28") + Dim srcA2 = " +Partial Class C + Sub F() + End Sub +End Class" + Dim srcB2 = " +Partial Class C + Sub G() + End Sub +End Class" - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.InitializerUpdate, "Red = 1 << 0", FeaturesResources.enum_value), - Diagnostic(RudeEditKind.InitializerUpdate, "Blue = 2 << 1", FeaturesResources.enum_value)) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("G"), preserveLocalVariables:=False) + }) + }) End Sub - Public Sub Enum_MemberInitializer_Update3() - Dim src1 = "Enum Color : Red = int.MinValue : End Enum" - Dim src2 = "Enum Color : Red = int.MaxValue : End Enum" - Dim edits = GetTopEdits(src1, src2) + Public Sub Type_DeleteInsert() + Dim srcA1 = " +Class C + Sub F() + End Sub +End Class - edits.VerifyEdits( - "Update [Red = int.MinValue]@13 -> [Red = int.MaxValue]@13") +Module M + Sub F() + End Sub +End Module - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.InitializerUpdate, "Red = int.MaxValue", FeaturesResources.enum_value)) - End Sub +Structure S + Sub F() + End Sub +End Structure - - Public Sub Enum_MemberInitializer_Insert() - Dim src1 = "Enum Color : Red : End Enum" - Dim src2 = "Enum Color : Red = 1 : End Enum" - Dim edits = GetTopEdits(src1, src2) +Interface I + Sub F() +End Interface - edits.VerifyEdits( - "Update [Red]@13 -> [Red = 1]@13") +Delegate Sub D() +" + Dim srcB1 = "" - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.InitializerUpdate, "Red = 1", FeaturesResources.enum_value)) + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("F")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("M").GetMember("F")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("S").GetMember("F")) + }) + }) End Sub - Public Sub Enum_MemberInitializer_Delete() - Dim src1 = "Enum Color : Red = 1 : End Enum" - Dim src2 = "Enum Color : Red : End Enum" - Dim edits = GetTopEdits(src1, src2) + Public Sub GenericType_DeleteInsert() + Dim srcA1 = " +Class C(Of T) + Sub F() + End Sub +End Class - edits.VerifyEdits( - "Update [Red = 1]@13 -> [Red]@13") +Structure S(Of T) + Sub F() + End Sub +End Structure - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.InitializerUpdate, "Red", FeaturesResources.enum_value)) +Interface I(Of T) + Sub F() +End Interface +" + Dim srcB1 = "" + + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + diagnostics:= + { + Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "Sub F()", FeaturesResources.method), + Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "Sub F()", FeaturesResources.method), + Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "Sub F()", FeaturesResources.method) + }) + }) End Sub - Public Sub Enum_Member_Insert1() - Dim src1 = "Enum Color : Red : End Enum" - Dim src2 = "Enum Color : Red : Blue : End Enum" - Dim edits = GetTopEdits(src1, src2) + Public Sub Type_DeleteInsert_NonInsertableMembers() + Dim srcA1 = " +MustInherit Class C + Implements I + Public MustOverride Sub AbstractMethod() - edits.VerifyEdits( - "Insert [Blue]@19") + Public Overridable Sub VirtualMethod() + End Sub - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Blue", FeaturesResources.enum_value)) - End Sub + Public Overrides Function ToString() As String + Return Nothing + End Function - - Public Sub Enum_Member_Insert2() - Dim src1 = "Enum Color : Red : End Enum" - Dim src2 = "Enum Color : Red : Blue : End Enum" - Dim edits = GetTopEdits(src1, src2) + Public Sub IG() Implements I.G + End Sub +End Class - edits.VerifyEdits( - "Insert [Blue]@19") +Interface I + Sub G() +End Interface +" + Dim srcB1 = "" - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Blue", FeaturesResources.enum_value)) + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("VirtualMethod")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("ToString")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("IG")) + }) + }) End Sub - Public Sub Enum_Member_Update() - Dim src1 = "Enum Color : Red : End Enum" - Dim src2 = "Enum Color : Orange : End Enum" - Dim edits = GetTopEdits(src1, src2) + Public Sub Type_DeleteInsert_DataMembers() + Dim srcA1 = " +Class C + Dim F1 = 1, F2 As New Object, F3 As Integer, F4 As New Object, F5(1, 2), F6? As Integer +End Class +" + Dim srcB1 = "" - edits.VerifyEdits( - "Update [Red]@13 -> [Orange]@13") + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Renamed, "Orange", FeaturesResources.enum_value)) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + }) End Sub - Public Sub Enum_Member_Delete() - Dim src1 = "Enum Color : Red : Blue : End Enum" - Dim src2 = "Enum Color : Red : End Enum" - Dim edits = GetTopEdits(src1, src2) + Public Sub Type_DeleteInsert_DataMembers_PartialSplit() + Dim srcA1 = " +Class C + Public x = 1 + Public y = 2 + Public z = 2 +End Class +" + Dim srcB1 = "" - edits.VerifyEdits( - "Delete [Blue]@19") + Dim srcA2 = " +Class C + Public x = 1 + Public y = 2 +End Class +" + Dim srcB2 = " +Partial Class C + Public z = 3 +End Class +" + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + }) + End sub - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Enum Color", FeaturesResources.enum_value)) - End Sub + + Public Sub Type_DeleteInsert_DataMembers_PartialMerge() + Dim srcA1 = " +Partial Class C + Public x = 1 + Public y = 2 +End Class +" + Dim srcB1 = " +Class C + Public z = 1 +End Class +" + + Dim srcA2 = " +Class C + Public x = 1 + Public y = 2 + Public z = 2 +End Class +" + + Dim srcB2 = " +" + ' note that accessors are not updated since they do not have bodies + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }), + DocumentResults() + }) + End sub #End Region -#Region "Delegates" +#Region "Enums" - Public Sub Delegates_NoModifiers_Insert() + Public Sub Enum_NoModifiers_Insert() Dim src1 = "" - Dim src2 = "Delegate Sub C()" + Dim src2 = "Enum C : End Enum" Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics() End Sub - Public Sub Delegates_NoModifiers_IntoNamespace_Insert() + Public Sub Enum_NoModifiers_IntoNamespace_Insert() Dim src1 = "Namespace N : End Namespace" - Dim src2 = "Namespace N : Delegate Sub C() : End Namespace" + Dim src2 = "Namespace N : Enum C : End Enum : End Namespace" Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics() End Sub - Public Sub Delegates_NoModifiers_IntoType_Insert() - Dim src1 = "Class N : End Class" - Dim src2 = "Class N : Delegate Sub C() : End Class" + Public Sub Enum_Name_Update() + Dim src1 = "Enum Color : Red = 1 : Blue = 2 : End Enum" + Dim src2 = "Enum Colors : Red = 1 : Blue = 2 : End Enum" Dim edits = GetTopEdits(src1, src2) - edits.VerifyRudeDiagnostics() + edits.VerifyEdits( + "Update [Enum Color]@0 -> [Enum Colors]@0") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Renamed, "Enum Colors", FeaturesResources.enum_)) End Sub - Public Sub Delegates_Rename() - Dim src1 = "Public Delegate Sub D()" - Dim src2 = "Public Delegate Sub Z()" + Public Sub Enum_Modifiers_Update() + Dim src1 = "Public Enum Color : Red = 1 : Blue = 2 : End Enum" + Dim src2 = "Enum Color : Red = 1 : Blue = 2 : End Enum" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Public Delegate Sub D()]@0 -> [Public Delegate Sub Z()]@0") + "Update [Public Enum Color]@0 -> [Enum Color]@0") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Renamed, "Public Delegate Sub Z()", FeaturesResources.delegate_)) + Diagnostic(RudeEditKind.ModifiersUpdate, "Enum Color", FeaturesResources.enum_)) End Sub - Public Sub Delegates_Update_Modifiers() - Dim src1 = "Public Delegate Sub D()" - Dim src2 = "Private Delegate Sub D()" + Public Sub Enum_BaseType_Insert() + Dim src1 = "Enum Color : Red = 1 : Blue = 2 : End Enum" + Dim src2 = "Enum Color As UShort : Red = 1 : Blue = 2 : End Enum" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Public Delegate Sub D()]@0 -> [Private Delegate Sub D()]@0") + "Insert [As UShort]@11") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Private Delegate Sub D()", FeaturesResources.delegate_)) + Diagnostic(RudeEditKind.Insert, "As UShort", VBFeaturesResources.as_clause)) End Sub - Public Sub Delegates_Update_ReturnType1() - Dim src1 = "Public Delegate Function D()" - Dim src2 = "Public Delegate Sub D()" + Public Sub Enum_BaseType_Update() + Dim src1 = "Enum Color As UShort : Red = 1 : Blue = 2 : End Enum" + Dim src2 = "Enum Color As Long : Red = 1 : Blue = 2 : End Enum" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Public Delegate Function D()]@0 -> [Public Delegate Sub D()]@0") + "Update [As UShort]@11 -> [As Long]@11") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeUpdate, "Public Delegate Sub D()", FeaturesResources.delegate_)) + Diagnostic(RudeEditKind.TypeUpdate, "Enum Color", FeaturesResources.enum_)) End Sub - Public Sub Delegates_Update_ReturnType2() - Dim src1 = "Public Delegate Function D() As Integer" - Dim src2 = "Public Delegate Sub D()" + Public Sub Enum_BaseType_Delete() + Dim src1 = "Enum Color As UShort : Red = 1 : Blue = 2 : End Enum" + Dim src2 = "Enum Color : Red = 1 : Blue = 2 : End Enum" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Public Delegate Function D() As Integer]@0 -> [Public Delegate Sub D()]@0", - "Delete [As Integer]@29") + "Delete [As UShort]@11") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeUpdate, "Public Delegate Sub D()", FeaturesResources.delegate_), - Diagnostic(RudeEditKind.Delete, "Public Delegate Sub D()", VBFeaturesResources.as_clause)) + Diagnostic(RudeEditKind.Delete, "Enum Color", VBFeaturesResources.as_clause)) End Sub - Public Sub Delegates_Update_ReturnType3() - Dim src1 = "Public Delegate Function D()" - Dim src2 = "Public Delegate Function D() As Integer" + Public Sub Enum_Attribute_Insert() + Dim src1 = "Enum E : End Enum" + Dim src2 = "Enum E : End Enum" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Insert [As Integer]@29") + "Insert []@0", "Insert [A]@1") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "As Integer", VBFeaturesResources.as_clause)) + Diagnostic(RudeEditKind.Insert, "", VBFeaturesResources.attributes)) End Sub - Public Sub Delegates_Update_ReturnType4() + Public Sub Enum_MemberAttribute_Delete() + Dim src1 = "Enum E : X : End Enum" + Dim src2 = "Enum E : X : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Delete []@9", "Delete [A]@10") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "X", VBFeaturesResources.attributes)) + End Sub + + + Public Sub Enum_MemberAttribute_Insert() + Dim src1 = "Enum E : X : End Enum" + Dim src2 = "Enum E : X : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Insert []@9", "Insert [A]@10") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "", VBFeaturesResources.attributes)) + End Sub + + + Public Sub Enum_MemberAttribute_Update() + Dim src1 = "Enum E : X : End Enum" + Dim src2 = "Enum E : X : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [A1]@10 -> [A2]@10") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Update, "A2", FeaturesResources.attribute)) + End Sub + + + Public Sub Enum_MemberInitializer_Update1() + Dim src1 = "Enum Color : Red = 1 : Blue = 2 : End Enum" + Dim src2 = "Enum Color : Red = 1 : Blue = 3 : End Enum" + + Dim edits = GetTopEdits(src1, src2) + edits.VerifyEdits( + "Update [Blue = 2]@23 -> [Blue = 3]@23") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.InitializerUpdate, "Blue = 3", FeaturesResources.enum_value)) + End Sub + + + Public Sub Enum_MemberInitializer_Update2() + Dim src1 = "Enum Color : Red = 1 : Blue = 2 : End Enum" + Dim src2 = "Enum Color : Red = 1 << 0 : Blue = 2 << 1 : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Red = 1]@13 -> [Red = 1 << 0]@13", + "Update [Blue = 2]@23 -> [Blue = 2 << 1]@28") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.InitializerUpdate, "Red = 1 << 0", FeaturesResources.enum_value), + Diagnostic(RudeEditKind.InitializerUpdate, "Blue = 2 << 1", FeaturesResources.enum_value)) + End Sub + + + Public Sub Enum_MemberInitializer_Update3() + Dim src1 = "Enum Color : Red = int.MinValue : End Enum" + Dim src2 = "Enum Color : Red = int.MaxValue : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Red = int.MinValue]@13 -> [Red = int.MaxValue]@13") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.InitializerUpdate, "Red = int.MaxValue", FeaturesResources.enum_value)) + End Sub + + + Public Sub Enum_MemberInitializer_Insert() + Dim src1 = "Enum Color : Red : End Enum" + Dim src2 = "Enum Color : Red = 1 : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Red]@13 -> [Red = 1]@13") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.InitializerUpdate, "Red = 1", FeaturesResources.enum_value)) + End Sub + + + Public Sub Enum_MemberInitializer_Delete() + Dim src1 = "Enum Color : Red = 1 : End Enum" + Dim src2 = "Enum Color : Red : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Red = 1]@13 -> [Red]@13") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.InitializerUpdate, "Red", FeaturesResources.enum_value)) + End Sub + + + Public Sub Enum_Member_Insert1() + Dim src1 = "Enum Color : Red : End Enum" + Dim src2 = "Enum Color : Red : Blue : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Insert [Blue]@19") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "Blue", FeaturesResources.enum_value)) + End Sub + + + Public Sub Enum_Member_Insert2() + Dim src1 = "Enum Color : Red : End Enum" + Dim src2 = "Enum Color : Red : Blue : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Insert [Blue]@19") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "Blue", FeaturesResources.enum_value)) + End Sub + + + Public Sub Enum_Member_Update() + Dim src1 = "Enum Color : Red : End Enum" + Dim src2 = "Enum Color : Orange : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Red]@13 -> [Orange]@13") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Renamed, "Orange", FeaturesResources.enum_value)) + End Sub + + + Public Sub Enum_Member_Delete() + Dim src1 = "Enum Color : Red : Blue : End Enum" + Dim src2 = "Enum Color : Red : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Delete [Blue]@19") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "Enum Color", DeletedSymbolDisplay(FeaturesResources.enum_value, "Blue"))) + End Sub +#End Region + +#Region "Delegates" + + Public Sub Delegates_NoModifiers_Insert() + Dim src1 = "" + Dim src2 = "Delegate Sub C()" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics() + End Sub + + + Public Sub Delegates_NoModifiers_IntoNamespace_Insert() + Dim src1 = "Namespace N : End Namespace" + Dim src2 = "Namespace N : Delegate Sub C() : End Namespace" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics() + End Sub + + + Public Sub Delegates_NoModifiers_IntoType_Insert() + Dim src1 = "Class N : End Class" + Dim src2 = "Class N : Delegate Sub C() : End Class" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics() + End Sub + + + Public Sub Delegates_Rename() + Dim src1 = "Public Delegate Sub D()" + Dim src2 = "Public Delegate Sub Z()" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Public Delegate Sub D()]@0 -> [Public Delegate Sub Z()]@0") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Renamed, "Public Delegate Sub Z()", FeaturesResources.delegate_)) + End Sub + + + Public Sub Delegates_Update_Modifiers() + Dim src1 = "Public Delegate Sub D()" + Dim src2 = "Private Delegate Sub D()" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Public Delegate Sub D()]@0 -> [Private Delegate Sub D()]@0") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "Private Delegate Sub D()", FeaturesResources.delegate_)) + End Sub + + + Public Sub Delegates_Update_ReturnType1() + Dim src1 = "Public Delegate Function D()" + Dim src2 = "Public Delegate Sub D()" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Public Delegate Function D()]@0 -> [Public Delegate Sub D()]@0") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.TypeUpdate, "Public Delegate Sub D()", FeaturesResources.delegate_)) + End Sub + + + Public Sub Delegates_Update_ReturnType2() + Dim src1 = "Public Delegate Function D() As Integer" + Dim src2 = "Public Delegate Sub D()" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Public Delegate Function D() As Integer]@0 -> [Public Delegate Sub D()]@0", + "Delete [As Integer]@29") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.TypeUpdate, "Public Delegate Sub D()", FeaturesResources.delegate_), + Diagnostic(RudeEditKind.Delete, "Public Delegate Sub D()", VBFeaturesResources.as_clause)) + End Sub + + + Public Sub Delegates_Update_ReturnType3() + Dim src1 = "Public Delegate Function D()" + Dim src2 = "Public Delegate Function D() As Integer" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Insert [As Integer]@29") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "As Integer", VBFeaturesResources.as_clause)) + End Sub + + + Public Sub Delegates_Update_ReturnType4() Dim src1 = "Public Delegate Function D() As Integer" Dim src2 = "Public Delegate Function D()" Dim edits = GetTopEdits(src1, src2) @@ -1330,110 +1903,114 @@ End Class Public Sub NestedClass_Insert_PInvoke_Syntactic() - Dim src1 As String = .Value +" - Dim src2 As String = .Value +" Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Declare Ansi Function A Lib ""A"" ()", FeaturesResources.method), - Diagnostic(RudeEditKind.Insert, "Declare Ansi Sub B Lib ""B"" ()", FeaturesResources.method)) + Diagnostic(RudeEditKind.InsertDllImport, "Declare Ansi Function A Lib ""A"" ()", FeaturesResources.method), + Diagnostic(RudeEditKind.InsertDllImport, "Declare Ansi Sub B Lib ""B"" ()", FeaturesResources.method)) End Sub Public Sub NestedClass_Insert_PInvoke_Semantic1() - Dim src1 As String = .Value +End Class" - Dim src2 As String = + Public Shared Function puts(c As String) As Integer End Function - + Public Shared Operator +(d As D, g As D) As Integer End Operator - + Public Shared Narrowing Operator CType(d As D) As Integer End Operator End Class -End Class -]]>.Value +End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.InsertDllImport, "puts"), - Diagnostic(RudeEditKind.InsertDllImport, "+"), - Diagnostic(RudeEditKind.InsertDllImport, "CType")) + edits.VerifySemantics( + diagnostics:= + { + Diagnostic(RudeEditKind.InsertDllImport, "Public Shared Function puts(c As String)", FeaturesResources.method), + Diagnostic(RudeEditKind.InsertDllImport, "Public Shared Operator +(d As D, g As D)", FeaturesResources.operator_), + Diagnostic(RudeEditKind.InsertDllImport, "Public Shared Narrowing Operator CType(d As D)", FeaturesResources.operator_) + }, + targetFrameworks:={TargetFramework.NetStandard20}) End Sub Public Sub NestedClass_Insert_PInvoke_Semantic2() - Dim src1 As String = .Value +End Class" - Dim src2 As String = + Private Shared Function puts(c As String) As Integer End Function -End Class -]]>.Value +End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.InsertDllImport, "puts")) + edits.VerifySemantics( + diagnostics:= + { + Diagnostic(RudeEditKind.InsertDllImport, "Private Shared Function puts(c As String)", FeaturesResources.method) + }, + targetFrameworks:={TargetFramework.NetStandard20}) End Sub Public Sub NestedClass_Insert_VirtualAbstract() - Dim src1 As String = + Dim src1 = " Imports System Imports System.Runtime.InteropServices Class C End Class -.Value +" - Dim src2 As String = + Dim src2 = " Imports System Imports System.Runtime.InteropServices @@ -1454,7 +2031,7 @@ Class C End Function End Class End Class -.Value +" Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics() End Sub @@ -1489,7 +2066,7 @@ End Class "Delete [()]@29") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Public Class C", FeaturesResources.method)) + Diagnostic(RudeEditKind.Delete, "Public Class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))) End Sub @@ -1503,74 +2080,824 @@ End Class "Insert [Public Class D]@17", "Move [Public Class X : End Class]@17 -> @34") - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "Public Class X", FeaturesResources.class_)) - End Sub + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Move, "Public Class X", FeaturesResources.class_)) + End Sub + + ''' + ''' A new generic type can be added whether it's nested and inherits generic parameters from the containing type, or top-level. + ''' + + public Sub NestedClassGeneric_Insert() + Dim src1 = " +Imports System +Class C(Of T) +End Class +" + Dim src2 = " +Imports System +Class C(Of T) + Class C + End Class + + Structure S + End Structure + + Enum N + A + End Enum + + Interface I + End Interface + + Class D + End Class + + Delegate Sub G() +End Class + +Class D(Of T) +End Class +" + Dim edits = GetTopEdits(src1, src2) + edits.VerifyRudeDiagnostics() + End Sub + + + Public Sub NestedEnum_InsertMember() + Dim src1 = " +Structure S + Enum N + A = 1 + End Enum +End Structure +" + Dim src2 = " +Structure S + Enum N + A = 1 + B = 2 + End Enum +End Structure +" + + Dim edits = GetTopEdits(src1, src2) + edits.VerifyEdits( + "Insert [B = 2]@40") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "B = 2", FeaturesResources.enum_value)) + End Sub + + + Public Sub NestedEnumInPartialType_InsertDelete() + Dim srcA1 = "Partial Structure S : End Structure" + Dim srcB1 = "Partial Structure S : Enum N : A = 1 : End Enum" + vbCrLf + "End Structure" + Dim srcA2 = "Partial Structure S : Enum N : A = 1 : End Enum" + vbCrLf + "End Structure" + Dim srcB2 = "Partial Structure S : End Structure" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) + End Sub + + + Public Sub NestedEnumInPartialType_InsertDeleteAndUpdateMember() + Dim srcA1 = "Partial Structure S : End Structure" + Dim srcB1 = "Partial Structure S : Enum N : A = 1 : End Enum" + vbCrLf + "End Structure" + Dim srcA2 = "Partial Structure S : Enum N : A = 2 : End Enum" + vbCrLf + "End Structure" + Dim srcB2 = "Partial Structure S : End Structure" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + diagnostics:= + { + Diagnostic(RudeEditKind.InitializerUpdate, "A = 2", FeaturesResources.enum_value) + }), + DocumentResults() + }) + End Sub + + + Public Sub NestedEnumInPartialType_InsertDeleteAndInsertMember() + Dim srcA1 = "Partial Structure S : End Structure" + Dim srcB1 = "Partial Structure S : Enum N : A = 1 : End Enum" + vbCrLf + "End Structure" + Dim srcA2 = "Partial Structure S : Enum N : A = 1 : B = 2 : End Enum" + vbCrLf + "End Structure" + Dim srcB2 = "Partial Structure S : End Structure" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.Insert, "B = 2", FeaturesResources.enum_value)}), + DocumentResults() + }) + End Sub + + + public sub NestedDelegateInPartialType_InsertDelete() + Dim srcA1 = "Partial Structure S : End Structure" + Dim srcB1 = "Partial Structure S : Delegate Sub D()" + vbCrLf + "End Structure" + Dim srcA2 = "Partial Structure S : Delegate Sub D()" + vbCrLf + "End Structure" + Dim srcB2 = "Partial Structure S : End Structure" + + ' delegate does not have any user-defined method body and this does not need a PDB update + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + ), + DocumentResults() + }) + End Sub + + + Public Sub NestedDelegateInPartialType_InsertDeleteAndChangeSignature() + Dim srcA1 = "Partial Structure S : End Structure" + Dim srcB1 = "Partial Structure S : Delegate Sub D()" + vbCrLf + "End Structure" + Dim srcA2 = "Partial Structure S : Delegate Sub D(a As Integer)" + vbCrLf + "End Structure" + Dim srcB2 = "Partial Structure S : End Structure" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + diagnostics:= + { + Diagnostic(RudeEditKind.Insert, "a As Integer", FeaturesResources.parameter) + }), + DocumentResults() + }) + End Sub + + + Public Sub NestedPartialTypeInPartialType_InsertDeleteAndChange() + Dim srcA1 = "Partial Structure S : Partial Class C" + vbCrLf + "Sub F1() : End Sub : End Class : End Structure" + Dim srcB1 = "Partial Structure S : Partial Class C" + vbCrLf + "Sub F2(x As Byte) : End Sub : End Class : End Structure" + Dim srcC1 = "Partial Structure S : End Structure" + + Dim srcA2 = "Partial Structure S : Partial Class C" + vbCrLf + "Sub F1() : End Sub : End Class : End Structure" + Dim srcB2 = "Partial Structure S : End Structure" + Dim srcC2 = "Partial Structure S : Partial Class C" + vbCrLf + "Sub F2(x As Integer) : End Sub : End Class : End Structure" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2)}, + { + DocumentResults(), + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.Delete, "Partial Structure S", DeletedSymbolDisplay(FeaturesResources.method, "F2(Byte)"))}), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("S").GetMember(Of NamedTypeSymbol)("C").GetMember("F2"))}) + }) + End Sub + + + Public Sub NestedPartialTypeInPartialType_InsertDeleteAndChange_BaseType() + Dim srcA1 = "Partial Class C : End Class" + Dim srcB1 = "" + Dim srcC1 = "Partial Class C : End Class" + + Dim srcA2 = "" + Dim srcB2 = "Partial Class C : Inherits D" + vbCrLf + "End Class" + Dim srcC2 = "Partial Class C : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2)}, + { + DocumentResults(), + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "Partial Class C", FeaturesResources.class_)}), + DocumentResults() + }) + End Sub + + + Public Sub NestedPartialTypeInPartialType_InsertDeleteAndChange_Attribute() + Dim srcA1 = "Partial Class C : End Class" + Dim srcB1 = "" + Dim srcC1 = "Partial Class C : End Class" + + Dim srcA2 = "" + Dim srcB2 = "Partial Class C : End Class" + Dim srcC2 = "Partial Class C : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2)}, + { + DocumentResults(), + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.Update, "Partial Class C", FeaturesResources.class_)}), + DocumentResults() + }) + End Sub + + + Public Sub NestedPartialTypeInPartialType_InsertDeleteAndChange_Constraint() + Dim srcA1 = "Partial Class C(Of T) : End Class" + Dim srcB1 = "" + Dim srcC1 = "Partial Class C(Of T) : End Class" + + Dim srcA2 = "" + Dim srcB2 = "Partial Class C(Of T As New) : End Class" + Dim srcC2 = "Partial Class C(Of T) : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2)}, + { + DocumentResults(), + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.Update, "Partial Class C(Of T As New)", FeaturesResources.class_)}), + DocumentResults() + }) + End Sub + + ''' + ''' Moves partial classes to different files while moving around their attributes and base interfaces. + ''' + + Public Sub NestedPartialTypeInPartialType_InsertDeleteRefactor() + Dim srcA1 = "Partial Class C : Implements I" + vbCrLf + "Sub F() : End Sub : End Class" + Dim srcB1 = "Partial Class C : Implements J" + vbCrLf + "Sub G() : End Sub : End Class" + Dim srcC1 = "" + Dim srcD1 = "" + + Dim srcA2 = "" + Dim srcB2 = "" + Dim srcC2 = "Partial Class C : Implements I, J" + vbCrLf + "Sub F() : End Sub : End Class" + Dim srcD2 = "Partial Class C" + vbCrLf + "Sub G() : End Sub : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2), GetTopEdits(srcD1, srcD2)}, + { + DocumentResults(), + DocumentResults(), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("F"))}), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("G"))}) + }) + End Sub + + ''' + ''' Moves partial classes to different files while moving around their attributes and base interfaces. + ''' Currently we do not support splitting attribute lists. + ''' + + Public Sub NestedPartialTypeInPartialType_InsertDeleteRefactor_AttributeListSplitting() + Dim srcA1 = "Partial Class C" + vbCrLf + "Sub F() : End Sub : End Class" + Dim srcB1 = "Partial Class C" + vbCrLf + "Sub G() : End Sub : End Class" + Dim srcC1 = "" + Dim srcD1 = "" + + Dim srcA2 = "" + Dim srcB2 = "" + Dim srcC2 = "Partial Class C" + vbCrLf + "Sub F() : End Sub : End Class" + Dim srcD2 = "Partial Class C" + vbCrLf + "Sub G() : End Sub : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2), GetTopEdits(srcD1, srcD2)}, + { + DocumentResults(), + DocumentResults(), + DocumentResults(diagnostics:={Diagnostic(RudeEditKind.Update, "Partial Class C", FeaturesResources.class_)}), + DocumentResults(diagnostics:={Diagnostic(RudeEditKind.Update, "Partial Class C", FeaturesResources.class_)}) + }) + End Sub + + + Public Sub NestedPartialTypeInPartialType_InsertDeleteChangeMember() + Dim srcA1 = "Partial Class C" + vbCrLf + "Sub F(Optional y As Integer = 1) : End Sub : End Class" + Dim srcB1 = "Partial Class C" + vbCrLf + "Sub G(Optional x As Integer = 1) : End Sub : End Class" + Dim srcC1 = "" + + Dim srcA2 = "" + Dim srcB2 = "Partial Class C" + vbCrLf + "Sub G(Optional x As Integer = 2) : End Sub : End Class" + Dim srcC2 = "Partial Class C" + vbCrLf + "Sub F(Optional y As Integer = 2) : End Sub : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2)}, + { + DocumentResults(), + DocumentResults(diagnostics:={Diagnostic(RudeEditKind.InitializerUpdate, "Optional x As Integer = 2", FeaturesResources.parameter)}), + DocumentResults(diagnostics:={Diagnostic(RudeEditKind.InitializerUpdate, "Optional y As Integer = 2", FeaturesResources.parameter)}) + }) + End Sub + + + Public Sub NestedPartialTypeInPartialType_InsertDeleteAndInsertVirtual() + Dim srcA1 = "Partial Interface I : Partial Class C" + vbCrLf + "Overridable Sub F1()" + vbCrLf + "End Sub : End Class : End Interface" + Dim srcB1 = "Partial Interface I : Partial Class C" + vbCrLf + "Overridable Sub F2()" + vbCrLf + "End Sub : End Class : End Interface" + Dim srcC1 = "Partial Interface I : Partial Class C" + vbCrLf + "End Class : End Interface" + Dim srcD1 = "Partial Interface I : Partial Class C" + vbCrLf + "End Class : End Interface" + Dim srcE1 = "Partial Interface I : End Interface" + Dim srcF1 = "Partial Interface I : End Interface" + + Dim srcA2 = "Partial Interface I : Partial Class C" + vbCrLf + "End Class : End Interface" + Dim srcB2 = "" + Dim srcC2 = "Partial Interface I : Partial Class C" + vbCrLf + "Overridable Sub F1()" + vbCrLf + "End Sub : End Class : End Interface" ' move existing virtual into existing partial decl + Dim srcD2 = "Partial Interface I : Partial Class C" + vbCrLf + "Overridable Sub N1()" + vbCrLf + "End Sub : End Class : End Interface" ' insert new virtual into existing partial decl + Dim srcE2 = "Partial Interface I : Partial Class C" + vbCrLf + "Overridable Sub F2()" + vbCrLf + "End Sub : End Class : End Interface" ' move existing virtual into a new partial decl + Dim srcF2 = "Partial Interface I : Partial Class C" + vbCrLf + "Overridable Sub N2()" + vbCrLf + "End Sub : End Class : End Interface" ' insert new virtual into new partial decl + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2), GetTopEdits(srcD1, srcD2), GetTopEdits(srcE1, srcE2), GetTopEdits(srcF1, srcF2)}, + { + DocumentResults(), + DocumentResults(), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("I").GetMember(Of NamedTypeSymbol)("C").GetMember("F1"))}), + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.InsertVirtual, "Overridable Sub N1()", FeaturesResources.method)}), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("I").GetMember(Of NamedTypeSymbol)("C").GetMember("F2"))}), + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.InsertVirtual, "Overridable Sub N2()", FeaturesResources.method)}) + }) + End Sub + +#End Region + +#Region "Namespaces" + + Public Sub NamespaceInsert() + Dim src1 = "" + Dim src2 = "Namespace C : End Namespace" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "Namespace C", FeaturesResources.namespace_)) + End Sub + + + Public Sub NamespaceDelete() + Dim src1 = "Namespace C : End Namespace" + Dim src2 = "" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, Nothing, FeaturesResources.namespace_)) + End Sub + + + Public Sub NamespaceMove1() + Dim src1 = "Namespace C : Namespace D : End Namespace : End Namespace" + Dim src2 = "Namespace C : End Namespace : Namespace D : End Namespace" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Move [Namespace D : End Namespace]@14 -> @30") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Move, "Namespace D", FeaturesResources.namespace_)) + End Sub + + + Public Sub NamespaceReorder1() + Dim src1 = "Namespace C : Namespace D : End Namespace : Class T : End Class : Namespace E : End Namespace : End Namespace" + Dim src2 = "Namespace C : Namespace E : End Namespace : Class T : End Class : Namespace D : End Namespace : End Namespace" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Reorder [Class T : End Class]@44 -> @44", + "Reorder [Namespace E : End Namespace]@66 -> @14") + + edits.VerifyRudeDiagnostics() + End Sub + + + Public Sub NamespaceReorder2() + Dim src1 = "Namespace C : " & + "Namespace D1 : End Namespace : " & + "Namespace D2 : End Namespace : " & + "Namespace D3 : End Namespace : " & + "Class T : End Class : " & + "Namespace E : End Namespace : " & + "End Namespace" + + Dim src2 = "Namespace C : " & + "Namespace E : End Namespace : " & + "Class T : End Class : " & + "Namespace D1 : End Namespace : " & + "Namespace D2 : End Namespace : " & + "Namespace D3 : End Namespace : " & + "End Namespace" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Reorder [Class T : End Class]@107 -> @44", + "Reorder [Namespace E : End Namespace]@129 -> @14") + + edits.VerifyRudeDiagnostics() + End Sub + +#End Region + +#Region "Members" + + + Public Sub PartialMember_DeleteInsert() + Dim srcA1 = " +Imports System + +Partial Class C + Dim F1 = 1, F2 As New Object, F3 As Integer, F4 As New Object, F5(1, 2), F6? As Integer + + Sub M() + End Sub + + Property P1(i As Integer) As Integer + Get + Return 1 + End Get + Set(value As Integer) + End Set + End Property + + Property P2 As Integer + Property P3 As New Object + + Event E1(sender As Object, e As EventArgs) + + Event E2 As Action + + Custom Event E3 As EventHandler + AddHandler(value As EventHandler) + End AddHandler + RemoveHandler(value As EventHandler) + End RemoveHandler + RaiseEvent(sender As Object, e As EventArgs) + End RaiseEvent + End Event + + Dim WithEvents WE As Object +End Class +" + Dim srcB1 = " +Imports System + +Partial Class C +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("M")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of PropertySymbol)("P1").GetMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of PropertySymbol)("P1").SetMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of EventSymbol)("E3").AddMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of EventSymbol)("E3").RemoveMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of EventSymbol)("E3").RaiseMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + }) + End Sub + + + Public Sub PartialMember_InsertDelete_MultipleDocuments() + Dim srcA1 = "Partial Class C : End Class" + Dim srcB1 = "Partial Class C" + vbCrLf + "Sub F() : End Sub : End Class" + Dim srcA2 = "Partial Class C" + vbCrLf + "Sub F() : End Sub : End Class" + Dim srcB2 = "Partial Class C : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("F"), preserveLocalVariables:=False) + }), + DocumentResults() + }) + End Sub + + + Public Sub PartialMember_DeleteInsert_MultipleDocuments() + Dim srcA1 = "Partial Class C" + vbCrLf + "Sub F() : End Sub : End Class" + Dim srcB1 = "Partial Class C : End Class" + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "Partial Class C" + vbCrLf + "Sub F() : End Sub : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F"), preserveLocalVariables:=False) + }) + }) + End Sub + + + Public Sub PartialMember_DeleteInsert_GenericMethod() + Dim srcA1 = "Partial Class C" + vbCrLf + "Sub F(Of T)() : End Sub : End Class" + Dim srcB1 = "Partial Class C : End Class" + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "Partial Class C" + vbCrLf + "Sub F(Of T)() : End Sub : End Class" + + ' TODO better message + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(diagnostics:= + { + Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "Sub F(Of T)()", FeaturesResources.method) + }) + }) + End Sub + + + Public Sub PartialMember_DeleteInsert_GenericType() + Dim srcA1 = "Partial Class C(Of T)" + vbCrLf + "Sub F(Of T)() : End Sub : End Class" + Dim srcB1 = "Partial Class C(Of T) : End Class" + Dim srcA2 = "Partial Class C(Of T) : End Class" + Dim srcB2 = "Partial Class C(Of T)" + vbCrLf + "Sub F(Of T)() : End Sub : End Class" + + ' TODO better message + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(diagnostics:= + { + Diagnostic(RudeEditKind.GenericMethodTriviaUpdate, "Sub F(Of T)()", FeaturesResources.method) + }) + }) + End Sub + + + Public Sub PartialNestedType_InsertDeleteAndChange() + Dim srcA1 = " +Partial Class C +End Class +" + Dim srcB1 = " +Partial Class C + Class D + Sub M() + End Sub + End Class + + Interface I + End Interface +End Class" + + Dim srcA2 = " +Partial Class C + Class D + Implements I + + Sub M() + End Sub + End Class + + Interface I + End Interface +End Class" + + Dim srcB2 = " +Partial Class C +End Class +" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + diagnostics:= + { + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "Class D", FeaturesResources.class_) + }), + DocumentResults() + }) + End Sub + + + Public Sub PartialMember_RenameInsertDelete() + ' The syntactic analysis for A And B produce rename edits since it doesn't see that the member was in fact moved. + ' TODO: Currently, we don't even pass rename edits to semantic analysis where we could handle them as updates. + + Dim srcA1 = "Partial Class C" + vbCrLf + "Sub F1() : End Sub : End Class" + Dim srcB1 = "Partial Class C" + vbCrLf + "Sub F2() : End Sub : End Class" + Dim srcA2 = "Partial Class C" + vbCrLf + "Sub F2() : End Sub : End Class" + Dim srcB2 = "Partial Class C" + vbCrLf + "Sub F1() : End Sub : End Class" + + ' current outcome + GetTopEdits(srcA1, srcA2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Renamed, "Sub F2()", FeaturesResources.method)) + GetTopEdits(srcB1, srcB2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Renamed, "Sub F1()", FeaturesResources.method)) + + ' correct outcome + 'EditAndContinueValidation.VerifySemantics( + ' { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + ' + ' { + ' DocumentResults(semanticEdits:= + ' { + ' SemanticEdit(SemanticEditKind.Update, c => c.GetMember(Of NamedTypeSymbol)("C").GetMember("F2")), + ' }), + ' + ' DocumentResults( + ' semanticEdits:= + ' { + ' SemanticEdit(SemanticEditKind.Update, c => c.GetMember(Of NamedTypeSymbol)("C").GetMember("F1")), + ' }) + ' }); + End Sub + + + Public Sub PartialMember_DeleteInsert_UpdateMethodBodyError() + Dim srcA1 = " +Imports System.Collections.Generic + +Partial Class C + Iterator Function F() As IEnumerable(Of Integer) + Yield 1 + End Function +End Class +" + Dim srcB1 = " +Imports System.Collections.Generic + +Partial Class C +End Class +" + + Dim srcA2 = " +Imports System.Collections.Generic + +Partial Class C +End Class +" + Dim srcB2 = " +Imports System.Collections.Generic + +Partial Class C + Iterator Function F() As IEnumerable(Of Integer) + Yield 1 + Yield 2 + End Function +End Class +" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(diagnostics:= + { + Diagnostic(RudeEditKind.Insert, "Yield 2", VBFeaturesResources.Yield_statement) + }) + }) + End Sub + + + Public Sub PartialMember_DeleteInsert_UpdatePropertyAccessors() + Dim srcA1 = " +Partial Class C + Property P As Integer + Get + Return 1 + End Get + Set(value As Integer) + Console.WriteLine(1) + End Set + End Property +End Class +" + Dim srcB1 = "Partial Class C: End Class" + + Dim srcA2 = "Partial Class C: End Class" + Dim srcB2 = " +Partial Class C + Property P As Integer + Get + Return 2 + End Get + Set(value As Integer) + Console.WriteLine(2) + End Set + End Property +End Class" -#End Region + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of PropertySymbol)("P").GetMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of PropertySymbol)("P").SetMethod) + }) + }) + End Sub -#Region "Namespaces" - Public Sub NamespaceInsert() - Dim src1 = "" - Dim src2 = "Namespace C : End Namespace" - Dim edits = GetTopEdits(src1, src2) + Public Sub PartialMember_DeleteInsert_UpdateAutoProperty() + Dim srcA1 = "Partial Class C" + vbCrLf + "Property P As Integer = 1 : End Class" + Dim srcB1 = "Partial Class C : End Class" - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Namespace C", FeaturesResources.namespace_)) + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "Partial Class C" + vbCrLf + "Property P As Integer = 2 : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + }) End Sub - Public Sub NamespaceMove1() - Dim src1 = "Namespace C : Namespace D : End Namespace : End Namespace" - Dim src2 = "Namespace C : End Namespace : Namespace D : End Namespace" - Dim edits = GetTopEdits(src1, src2) + Public Sub PartialMember_DeleteInsert_AddFieldInitializer() + Dim srcA1 = "Partial Class C" + vbCrLf + "Dim P As Integer : End Class" + Dim srcB1 = "Partial Class C : End Class" - edits.VerifyEdits( - "Move [Namespace D : End Namespace]@14 -> @30") + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "Partial Class C" + vbCrLf + "Dim P As Integer = 1 : End Class" - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "Namespace D", FeaturesResources.namespace_)) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + }) End Sub - Public Sub NamespaceReorder1() - Dim src1 = "Namespace C : Namespace D : End Namespace : Class T : End Class : Namespace E : End Namespace : End Namespace" - Dim src2 = "Namespace C : Namespace E : End Namespace : Class T : End Class : Namespace D : End Namespace : End Namespace" - Dim edits = GetTopEdits(src1, src2) + Public Sub PartialMember_DeleteInsert_RemoveFieldInitializer() + Dim srcA1 = "Partial Class C" + vbCrLf + "Dim P As Integer = 1 : End Class" + Dim srcB1 = "Partial Class C : End Class" - edits.VerifyEdits( - "Reorder [Class T : End Class]@44 -> @44", - "Reorder [Namespace E : End Namespace]@66 -> @14") + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "Partial Class C" + vbCrLf + "Dim P As Integer : End Class" - edits.VerifyRudeDiagnostics() + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + }) End Sub - Public Sub NamespaceReorder2() - Dim src1 = "Namespace C : " & - "Namespace D1 : End Namespace : " & - "Namespace D2 : End Namespace : " & - "Namespace D3 : End Namespace : " & - "Class T : End Class : " & - "Namespace E : End Namespace : " & - "End Namespace" - - Dim src2 = "Namespace C : " & - "Namespace E : End Namespace : " & - "Class T : End Class : " & - "Namespace D1 : End Namespace : " & - "Namespace D2 : End Namespace : " & - "Namespace D3 : End Namespace : " & - "End Namespace" + Public Sub PartialMember_DeleteInsert_ConstructorWithInitializers() + Dim srcA1 = " +Partial Class C + Dim F = 1 - Dim edits = GetTopEdits(src1, src2) + Sub New(x As Integer) + F = x + End Sub +End Class" + Dim srcB1 = " +Partial Class C +End Class" - edits.VerifyEdits( - "Reorder [Class T : End Class]@107 -> @44", - "Reorder [Namespace E : End Namespace]@129 -> @14") + Dim srcA2 = " +Partial Class C + Dim F = 1 +End Class" + Dim srcB2 = " +Partial Class C + Sub New(x As Integer) + F = x + 1 + End Sub +End Class" - edits.VerifyRudeDiagnostics() + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + }) End Sub #End Region @@ -1661,7 +2988,7 @@ End Class "Delete [()]@15") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.method)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))) End Sub @@ -1672,7 +2999,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Interface C", FeaturesResources.method)) + Diagnostic(RudeEditKind.Delete, "Interface C", DeletedSymbolDisplay(FeaturesResources.method, "Goo()"))) End Sub @@ -1691,7 +3018,7 @@ End Class "Delete [As Integer]@18") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.method)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "goo(Integer)"))) End Sub @@ -1712,7 +3039,7 @@ End Class "Delete [As Integer]@32") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.method)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "goo(Integer)"))) End Sub @@ -1800,7 +3127,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "MustOverride Sub F", FeaturesResources.method)) + Diagnostic(RudeEditKind.InsertVirtual, "MustOverride Sub F", FeaturesResources.method)) End Sub @@ -1814,6 +3141,36 @@ End Class Diagnostic(RudeEditKind.InsertVirtual, "Overrides Sub F", FeaturesResources.method)) End Sub + + Public Sub ExternMethodDeleteInsert() + Dim srcA1 = " +Imports System +Imports System.Runtime.InteropServices + +Class C + + Public Shared Function puts(c As String) As Integer + End Function +End Class" + Dim srcA2 = " +Imports System +Imports System.Runtime.InteropServices +" + + Dim srcB1 = srcA2 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("puts")) + }) + }) + End Sub + Public Sub Method_Reorder1() Dim src1 = "Class C : " & vbLf & "Sub f(a As Integer, b As Integer)" & vbLf & "a = b : End Sub : " & vbLf & "Sub g() : End Sub : End Class" @@ -1866,7 +3223,7 @@ End Class "Delete [As Integer]@55") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.method)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "f(Integer, Integer)"))) End Sub @@ -2442,14 +3799,14 @@ End Class Public Sub MethodInsert_Handles_Clause() - Dim src1 = "Class C : Event E1 As Action" & vbLf & "End Class" - Dim src2 = "Class C : Event E1 As Action" & vbLf & "Private Sub Goo() Handles Me.E1 : End Sub : End Class" + Dim src1 = "Class C : Event E1 As System.Action" & vbLf & "End Class" + Dim src2 = "Class C : Event E1 As System.Action" & vbLf & "Private Sub Goo() Handles Me.E1 : End Sub : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Insert [Private Sub Goo() Handles Me.E1 : End Sub]@29", - "Insert [Private Sub Goo() Handles Me.E1]@29", - "Insert [()]@44") + "Insert [Private Sub Goo() Handles Me.E1 : End Sub]@36", + "Insert [Private Sub Goo() Handles Me.E1]@36", + "Insert [()]@51") edits.VerifyRudeDiagnostics( Diagnostic(RudeEditKind.InsertHandlesClause, "Private Sub Goo()", FeaturesResources.method)) @@ -2483,6 +3840,295 @@ End Class edits.VerifyRudeDiagnostics() End Sub + + + Public Sub PartialMethod_DeleteInsert_DefinitionPart() + Dim srcA1 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + Dim srcB1 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcC1 = "Partial Class C : End Class" + + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "partial class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcC2 = "partial class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2)}, + { + DocumentResults(), + DocumentResults(), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F").PartialImplementationPart)}) + }) + End Sub + + + Public Sub PartialMethod_DeleteInsert_ImplementationPart() + Dim srcA1 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcB1 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + Dim srcC1 = "Partial Class C : End Class" + + Dim srcA2 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcB2 = "Partial Class C : End Class" + Dim srcC2 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2)}, + { + DocumentResults(), + DocumentResults(), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F").PartialImplementationPart)}) + }) + End Sub + + + Public Sub PartialMethod_Swap_ImplementationAndDefinitionParts() + Dim srcA1 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcB1 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + + Dim srcA2 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + Dim srcB2 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + + ' TODO: current + GetTopEdits(srcA1, srcA2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.ModifiersUpdate, "Private Sub F()", FeaturesResources.method)) + GetTopEdits(srcB1, srcB2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.ModifiersUpdate, "Partial Private Sub F()", FeaturesResources.method)) + + ' TODO: correct + ' EditAndContinueValidation.VerifySemantics( + ' { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + ' + ' { + ' DocumentResults(), + ' DocumentResults( + ' semanticEdits:= { SemanticEdit(SemanticEditKind.Update, c => c.GetMember(Of NamedTypeSymbol)("C").GetMember("F")) }), + ' }); + End Sub + + + Public Sub PartialMethod_DeleteImplementation() + Dim srcA1 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcB1 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + + Dim srcA2 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcB2 = "Partial Class C : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.Delete, "Partial Class C", DeletedSymbolDisplay(FeaturesResources.method, "F()"))}) + }) + End Sub + + + Public Sub PartialMethod_DeleteBoth() + Dim srcA1 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcB1 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "Partial Class C : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.Delete, "Partial Class C", DeletedSymbolDisplay(FeaturesResources.method, "F()"))}) + }) + End Sub + + + Public Sub PartialMethod_DeleteInsertBoth() + Dim srcA1 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcB1 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + Dim srcC1 = "Partial Class C : End Class" + Dim srcD1 = "Partial Class C : End Class" + + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "Partial Class C : End Class" + Dim srcC2 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcD2 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2), GetTopEdits(srcD1, srcD2)}, + { + DocumentResults(), + DocumentResults(), + DocumentResults(), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F").PartialImplementationPart)}) + }) + End Sub + + + Public Sub PartialMethod_Insert() + Dim srcA1 = "Partial Class C : End Class" + Dim srcB1 = "Partial Class C : End Class" + + Dim srcA2 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" + Dim srcB2 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F").PartialImplementationPart)}) + }) + End Sub +#End Region + +#Region "Operators" + + + Public Sub OperatorInsert() + Dim src1 = " +Class C +End Class +" + Dim src2 = " +Class C + Public Shared Operator +(d As C, g As C) As Integer + Return Nothing + End Operator + + Public Shared Narrowing Operator CType(d As C) As Integer + Return Nothing + End Operator +End Class +" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertOperator, "Public Shared Operator +(d As C, g As C)", FeaturesResources.operator_), + Diagnostic(RudeEditKind.InsertOperator, "Public Shared Narrowing Operator CType(d As C)", FeaturesResources.operator_)) + End Sub + + + Public Sub OperatorDelete() + Dim src1 = " +Class C + Public Shared Operator +(d As C, g As C) As Integer + Return Nothing + End Operator + + Public Shared Narrowing Operator CType(d As C) As Integer + Return Nothing + End Operator +End Class +" + Dim src2 = " +Class C +End Class" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.operator_, "+(C, C)")), + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.operator_, "CType(C)"))) + End Sub + + + Public Sub OperatorInsertDelete() + Dim srcA1 = " +Partial Class C + Public Shared Narrowing Operator CType(d As C) As Integer + Return Nothing + End Operator +End Class +" + Dim srcB1 = " +Partial Class C + Public Shared Operator +(d As C, g As C) As Integer + Return Nothing + End Operator +End Class +" + + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("op_Addition")) + }), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("op_Implicit")) + }) + }) + End Sub + + + Public Sub OperatorUpdate() + Dim src1 = " +Class C + Public Shared Narrowing Operator CType(d As C) As Integer + Return 0 + End Operator + + Public Shared Operator +(d As C, g As C) As Integer + Return 0 + End Operator +End Class +" + Dim src2 = " +Class C + Public Shared Narrowing Operator CType(d As C) As Integer + Return 1 + End Operator + + Public Shared Operator +(d As C, g As C) As Integer + Return 1 + End Operator +End Class" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifySemantics(ActiveStatementsDescription.Empty, + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("op_Explicit")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("op_Addition")) + }) + End Sub + + + Public Sub OperatorReorder() + Dim src1 = " +Class C + Public Shared Narrowing Operator CType(d As C) As Integer + Return 0 + End Operator + + Public Shared Operator +(d As C, g As C) As Integer + Return 0 + End Operator +End Class +" + Dim src2 = " +Class C + Public Shared Operator +(d As C, g As C) As Integer + Return 0 + End Operator + + Public Shared Narrowing Operator CType(d As C) As Integer + Return 0 + End Operator +End Class +" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Reorder [Public Shared Operator +(d As C, g As C) As Integer + Return 0 + End Operator]@116 -> @15") + + edits.VerifyRudeDiagnostics() + End Sub #End Region #Region "Constructors" @@ -2555,7 +4201,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.constructor)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(VBFeaturesResources.Shared_constructor, "New()"))) End Sub @@ -2565,7 +4211,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Module C", FeaturesResources.constructor)) + Diagnostic(RudeEditKind.Delete, "Module C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) End Sub @@ -2575,7 +4221,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -2585,47 +4231,39 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) - End Sub - - - Public Sub InstanceCtorDelete_Private1() - Dim src1 = "Class C" & vbLf & "Private Sub New() : End Sub : End Class" - Dim src2 = "Class C : End Class" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.constructor)) + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub - - Public Sub InstanceCtorDelete_Protected() - Dim src1 = "Class C" & vbLf & "Protected Sub New() : End Sub : End Class" + + + + + + Public Sub InstanceCtorDelete_NonPublic(visibility As String) + Dim src1 = "Class C" & vbLf & visibility & " Sub New() : End Sub : End Class" Dim src2 = "Class C : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.constructor)) + Diagnostic(RudeEditKind.ChangingVisibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) End Sub - Public Sub InstanceCtorDelete_Internal() - Dim src1 = "Class C" & vbLf & "Friend Sub New() : End Sub : End Class" - Dim src2 = "Class C : End Class" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.constructor)) - End Sub + Public Sub InstanceCtorDelete_Public_PartialWithInitializerUpdate() + Dim srcA1 = "Class C" & vbLf & "Public Sub New() : End Sub : End Class" + Dim srcB1 = "Class C" & vbLf & "Dim x = 1 : End Class" - - Public Sub InstanceCtorDelete_ProtectedInternal() - Dim src1 = "Class C" & vbLf & "Protected Friend Sub New() : End Sub : End Class" - Dim src2 = "Class C : End Class" - Dim edits = GetTopEdits(src1, src2) + Dim srcA2 = "Class C : End Class" + Dim srcB2 = "Class C" & vbLf & "Dim x = 2 : End Class" - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.constructor)) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True)}), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True)}) + }) End Sub @@ -2666,11 +4304,13 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "End Class" Dim srcB2 = "Partial Class C" & vbLf & "Sub New() : End Sub : End Class" - EditAndContinueValidation.VerifySemantics( + ' no change in document A + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:= { - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + DocumentResults(), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) }) End Sub @@ -2682,7 +4322,7 @@ End Class edits.VerifySemantics( ActiveStatementsDescription.Empty, - expectedSemanticEdits:= + semanticEdits:= { SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(Function(m) m.Parameters.IsEmpty)) }) @@ -2696,52 +4336,28 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C" & vbLf & "Sub New(a As Integer) : End Sub : End Class" - EditAndContinueValidation.VerifySemantics( + ' no change in document B + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:= { - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(Function(m) m.Parameters.IsEmpty)) + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(Function(m) m.Parameters.IsEmpty))}), + DocumentResults() }) End Sub - - Public Sub InstanceCtorInsert_Private_Implicit1() - Dim src1 = "Class C : End Class" - Dim src2 = "Class C" & vbLf & "Private Sub New() : End Sub : End Class" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Private Sub New()")) - End Sub - - - Public Sub InstanceCtorInsert_Protected_PublicImplicit() - Dim src1 = "Class C : End Class" - Dim src2 = "Class C" & vbLf & "Protected Sub New() : End Sub : End Class" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Protected Sub New()")) - End Sub - - - Public Sub InstanceCtorInsert_Internal_PublicImplicit() + + + + + + Public Sub InstanceCtorInsert_Private_Implicit1(visibility As String) Dim src1 = "Class C : End Class" - Dim src2 = "Class C" & vbLf & "Friend Sub New() : End Sub : End Class" + Dim src2 = "Class C" & vbLf & visibility & " Sub New() : End Sub : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Friend Sub New()")) - End Sub - - - Public Sub InstanceCtorInsert_Internal_ProtectedImplicit() - Dim src1 = "MustInherit Class C : End Class" - Dim src2 = "MustInherit Class C" & vbLf & "Friend Sub New() : End Sub : End Class" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Friend Sub New()")) + Diagnostic(RudeEditKind.ChangingVisibility, visibility & " Sub New()", FeaturesResources.constructor)) End Sub @@ -2765,28 +4381,13 @@ End Class InstanceConstructors.Single(Function(ctor) ctor.DeclaredAccessibility = Accessibility.Private))}) End Sub - - Public Sub InstanceCtorInsert_Internal_NoImplicit() - Dim src1 = "Class C" & vbLf & "Public Sub New(a As Integer) : End Sub : End Class" - Dim src2 = "Class C" & vbLf & "Public Sub New(a As Integer) : End Sub : " & vbLf & "Friend Sub New() : End Sub : End Class" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifySemanticDiagnostics() - End Sub - - - Public Sub InstanceCtorInsert_Protected_NoImplicit() - Dim src1 = "Class C" & vbLf & "Public Sub New(a As Integer) : End Sub : End Class" - Dim src2 = "Class C" & vbLf & "Public Sub New(a As Integer) : End Sub : " & vbLf & "Protected Sub New() : End Sub : End Class" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifySemanticDiagnostics() - End Sub - - - Public Sub InstanceCtorInsert_FriendProtected_NoImplicit() + + + + + Public Sub InstanceCtorInsert_Internal_NoImplicit(visibility As String) Dim src1 = "Class C" & vbLf & "Public Sub New(a As Integer) : End Sub : End Class" - Dim src2 = "Class C" & vbLf & "Public Sub New(a As Integer) : End Sub : " & vbLf & "Friend Protected Sub New() : End Sub : End Class" + Dim src2 = "Class C" & vbLf & "Public Sub New(a As Integer) : End Sub : " & vbLf & visibility & " Sub New() : End Sub : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics() @@ -2800,22 +4401,33 @@ End Class Dim srcA2 = "Partial Class C : End Class" Dim srcB2 = "Partial Class C" & vbLf & "Shared Sub New() : End Sub : End Class" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) + { + DocumentResults(), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) + }) End Sub - Public Sub ModuleCtor_Partial_Delete() + Public Sub ModuleCtor_Partial_DeleteInsert() Dim srcA1 = "Partial Module C" & vbLf & "Sub New() : End Sub : End Module" Dim srcB1 = "Partial Module C : End Module" Dim srcA2 = "Partial Module C : End Module" Dim srcB2 = "Partial Module C" & vbLf & "Sub New() : End Sub : End Module" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) + { + DocumentResults( + ), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) + }) End Sub @@ -2826,9 +4438,15 @@ End Class Dim srcA2 = "Partial Class C : End Class" Dim srcB2 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + { + DocumentResults( + ), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + }) End Sub @@ -2839,9 +4457,14 @@ End Class Dim srcA2 = "Partial Class C : End Class" Dim srcB2 = "Partial Class C" & vbLf & "Public Sub New() : End Sub : End Class" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + { + DocumentResults(), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + }) End Sub @@ -2852,9 +4475,14 @@ End Class Dim srcA2 = "Partial Class C : End Class" Dim srcB2 = "Partial Class C" & vbLf & "Public Sub New() : End Sub : End Class" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be reported as rude edit in the other document where it was inserted back with changed visibility + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedDiagnostics:={Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Public Sub New()")}) + { + DocumentResults(), + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.ModifiersUpdate, "Public Sub New()", FeaturesResources.constructor)}) + }) End Sub @@ -2865,9 +4493,15 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Shared Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be reported as rude edit in the other document where it was inserted back with changed visibility + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) + { + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}), + DocumentResults( + ) + }) End Sub @@ -2878,9 +4512,15 @@ End Class Dim srcA2 = "Partial Module C" & vbLf & "Sub New() : End Sub : End Module" Dim srcB2 = "Partial Module C : End Module" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) + { + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}), + DocumentResults( + ) + }) End Sub @@ -2891,9 +4531,15 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + { + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}), + DocumentResults( + ) + }) End Sub @@ -2904,9 +4550,15 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + { + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}), + DocumentResults( + ) + }) End Sub @@ -2917,9 +4569,15 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Friend Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + { + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}), + DocumentResults( + ) + }) End Sub @@ -2930,35 +4588,39 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Friend Sub New()" & vbLf & "Console.WriteLine(1) : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + { + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}), + DocumentResults() + }) End Sub - - Public Sub InstanceCtor_Partial_InsertPublicDeletePrivate() - Dim srcA1 = "Partial Class C : End Class" - Dim srcB1 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" - - Dim srcA2 = "Partial Class C" & vbLf & "Sub New() : End Sub : End Class" - Dim srcB2 = "Partial Class C : End Class" - - EditAndContinueValidation.VerifySemantics( - {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedDiagnostics:={Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Sub New()")}) - End Sub + + + + + Public Sub InstanceCtor_Partial_VisibilityUpdate(visibility As String) + If visibility.Length > 0 Then + visibility &= " " + End If - - Public Sub InstanceCtor_Partial_InsertInternalDeletePrivate() Dim srcA1 = "Partial Class C : End Class" Dim srcB1 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" - Dim srcA2 = "Partial Class C" & vbLf & "Friend Sub New() : End Sub : End Class" + Dim srcA2 = "Partial Class C" & vbLf & visibility & "Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - EditAndContinueValidation.VerifySemantics( + ' delete of the constructor in partial part will be reported as rude edit in the other document where it was inserted back with changed visibility + VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedDiagnostics:={Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Friend Sub New()")}) + { + DocumentResults( + diagnostics:={Diagnostic(RudeEditKind.ModifiersUpdate, visibility & "Sub New()", FeaturesResources.constructor)}), + DocumentResults() + }) End Sub @@ -3175,9 +4837,8 @@ End Class Dim edits = GetTopEdits(src1, src2) Dim syntaxMap = GetSyntaxMap(src1, src2) - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(), syntaxMap(0))}) + edits.VerifySemanticDiagnostics( + {Diagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, "Sub New()")}) End Sub @@ -3230,37 +4891,245 @@ End Class End Sub - Public Sub Constructor_SemanticError_Partial() - Dim src1 = " + public sub PartialTypes_ConstructorWithInitializerUpdates() + Dim srcA1 = " +Imports System + +Partial Class C + Sub New(arg As Integer) + Console.WriteLine(0) + End Sub + + Sub New(arg As Boolean) + Console.WriteLine(1) + End Sub +End Class +" + Dim srcB1 = " +Imports System + +Partial Class C + Dim a = 1 + + Sub New(arg As UInteger) + Console.WriteLine(2) + End Sub +End Class +" + + Dim srcA2 = " +Imports System + +Partial Class C + Sub New(arg As Integer) + Console.WriteLine(0) + End Sub + + Sub New(arg As Boolean) + Console.WriteLine(1) + End Sub +End Class +" + Dim srcB2 = " +Imports System + +Partial Class C + Dim a = 2 ' updated field initializer + + Sub New(arg As UInteger) + Console.WriteLine(2) + End Sub + + Sub New(arg As Byte) + Console.WriteLine(3) ' new ctor + End Sub +End Class +" + Dim syntaxMapB = GetSyntaxMap(srcB1, srcB2)(0) + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(Function(m) m.Parameters.Single().Type.Name = "Int32"), syntaxMap:=syntaxMapB), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(Function(m) m.Parameters.Single().Type.Name = "Boolean"), syntaxMap:=syntaxMapB), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(Function(m) m.Parameters.Single().Type.Name = "UInt32"), syntaxMap:=syntaxMapB), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(Function(m) m.Parameters.Single().Type.Name = "Byte"), syntaxMap:=Nothing) + }) + }) + End Sub + + + Public Sub PartialTypes_ConstructorWithInitializerUpdates_SemanticErrors() + Dim srcA1 = " +Imports System + +Partial Class C + Sub New(arg As Integer) + Console.WriteLine(0) + End Sub + + Sub New(arg As Integer) + Console.WriteLine(1) + End Sub +End Class +" + Dim srcB1 = " +Imports System + +Partial Class C + Dim a = 1 +End Class +" + + Dim srcA2 = " +Imports System + +Partial Class C + Sub New(arg As Integer) + Console.WriteLine(0) + End Sub + + Sub New(arg As Integer) + Console.WriteLine(1) + End Sub +End Class +" + Dim srcB2 = " +Imports System + +Partial Class C + Dim a = 1 + + Sub New(arg As Integer) + Console.WriteLine(2) + End Sub +End Class +" + + ' The actual edits do not matter since there are semantic errors in the compilation. + ' We just should not crash. + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(diagnostics:=Array.Empty(Of RudeEditDiagnosticDescription)()) + }) + End Sub + + + Public Sub Constructor_SemanticError_Partial() + Dim src1 = " +Partial Class C + Partial Sub New(x As Integer) + End Sub +End Class + +Class C + Partial Sub New(x As Integer) + System.Console.WriteLine(1) + End Sub +End Class +" + Dim src2 = " +Partial Class C + Partial Sub New(x As Integer) + End Sub +End Class + +Class C + Partial Sub New(x As Integer) + System.Console.WriteLine(2) + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics(ActiveStatementsDescription.Empty, semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.First(), preserveLocalVariables:=True) + }) + End Sub + + + public sub PartialDeclaration_Delete() + Dim srcA1 = " +Partial Class C + Sub New() + End Sub + + Sub F() + End Sub +End Class +" + Dim srcB1 = " +Partial Class C + Dim x = 1 +End Class +" + + Dim srcA2 = "" + Dim srcB2 = " +Partial Class C + Dim x = 2 + + Sub F() + End Sub +End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True)}), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F"), partialType:="C"), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True) + }) + }) + End Sub + + + Public Sub PartialDeclaration_Insert() + Dim srcA1 = "" + Dim srcB1 = " Partial Class C - Partial Sub New(x As Integer) - End Sub -End Class + Dim x = 1 -Class C - Partial Sub New(x As Integer) - System.Console.WriteLine(1) + Sub F() End Sub End Class - " - Dim src2 = " + Dim srcA2 = " Partial Class C - Partial Sub New(x As Integer) + Public Sub New() End Sub -End Class -Class C - Partial Sub New(x As Integer) - System.Console.WriteLine(2) + Sub F() End Sub +End Class" + Dim srcB2 = " +Partial Class C + Dim x = 2 End Class " - Dim edits = GetTopEdits(src1, src2) - edits.VerifySemantics(ActiveStatementsDescription.Empty, expectedSemanticEdits:= - { - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Skip(1).First(), preserveLocalVariables:=True) - }) + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F"), partialType:="C"), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True) + }), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True)}) + }) End Sub #End Region @@ -3330,7 +5199,7 @@ End Class "Delete [As Integer]@49") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.method)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "Goo()"))) End Sub @@ -3345,7 +5214,7 @@ End Class "Insert [As Integer]@49") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Declare Ansi Function Goo Lib ""Bar"" ()", FeaturesResources.method)) + Diagnostic(RudeEditKind.InsertDllImport, "Declare Ansi Function Goo Lib ""Bar"" ()", FeaturesResources.method)) End Sub @@ -3360,7 +5229,7 @@ End Class "Insert [As Integer]@57") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Private Declare Ansi Function Goo Lib ""Bar"" ()", FeaturesResources.method)) + Diagnostic(RudeEditKind.InsertDllImport, "Private Declare Ansi Function Goo Lib ""Bar"" ()", FeaturesResources.method)) End Sub @@ -3370,7 +5239,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Declare Ansi Sub ExternSub Lib ""ExternDLL""()", FeaturesResources.method)) + Diagnostic(RudeEditKind.InsertDllImport, "Declare Ansi Sub ExternSub Lib ""ExternDLL""()", FeaturesResources.method)) End Sub @@ -3380,7 +5249,23 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Declare Ansi Sub ExternSub Lib ""ExternDLL""", FeaturesResources.method)) + Diagnostic(RudeEditKind.InsertDllImport, "Declare Ansi Sub ExternSub Lib ""ExternDLL""", FeaturesResources.method)) + End Sub + + + Public Sub Declare_DeleteInsert() + Dim srcA1 = "Module M : Declare Ansi Sub ExternSub Lib ""ExternDLL"" : End Module" + Dim srcB1 = "Module M : End Module" + + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) End Sub #End Region @@ -3398,7 +5283,7 @@ End Class Diagnostic(RudeEditKind.Renamed, "b", FeaturesResources.field)) End Sub - + Public Sub FieldUpdate_Rename2() Dim src1 = "Class C : Dim a1(), b1? As Integer, c1(1,2) As New D() : End Class" Dim src2 = "Class C : Dim a2(), b2? As Integer, c2(1,2) As New D() : End Class" @@ -3440,9 +5325,7 @@ End Class "Move [c]@27 -> @17", "Delete [c]@27") - ' TODO: We could check that the types and order of b and c haven't changed. - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "c", FeaturesResources.field)) + edits.VerifySemantics() End Sub @@ -3458,9 +5341,7 @@ End Class "Delete [c As Object]@27", "Delete [As Object]@29") - ' TODO: We could check that the types and order of b and c haven't changed. - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "c", FeaturesResources.field)) + edits.VerifySemantics() End Sub @@ -3476,9 +5357,7 @@ End Class "Move [c]@17 -> @27", "Insert [As Object]@29") - ' TODO: We could check that the types and order of b and c haven't changed. - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "c", FeaturesResources.field)) + edits.VerifySemantics() End Sub @@ -3492,9 +5371,7 @@ End Class "Update [c As Object]@30 -> [b, c As Object]@27", "Move [b]@17 -> @27") - ' TODO: We could check that the types and order of b and c haven't changed. - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "b", FeaturesResources.field)) + edits.VerifySemantics() End Sub @@ -3508,9 +5385,7 @@ End Class "Update [b, c As Object]@27 -> [c As Object]@30", "Move [b]@27 -> @17") - ' TODO: We could check that the types and order of b and c haven't changed. - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "b", FeaturesResources.field)) + edits.VerifySemantics() End Sub @@ -3522,8 +5397,7 @@ End Class edits.VerifyEdits( "Reorder [b As Object]@27 -> @14") - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "b As Object", FeaturesResources.field)) + edits.VerifySemantics() End Sub @@ -3535,8 +5409,17 @@ End Class edits.VerifyEdits( "Reorder [b, c As Object]@27 -> @14") + edits.VerifySemantics() + End Sub + + + Public Sub Field_VariableMove_TypeChange() + Dim src1 = "Class C : Dim a As Object, b, c As Object : End Class" + Dim src2 = "Class C : Dim a, b As Integer, c As Object : End Class" + Dim edits = GetTopEdits(src1, src2) + edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Move, "b, c As Object", FeaturesResources.field)) + Diagnostic(RudeEditKind.TypeUpdate, "a, b As Integer", FeaturesResources.field)) End Sub @@ -3552,7 +5435,7 @@ End Class "Delete [As Object]@29") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Dim b As Object", FeaturesResources.field)) + Diagnostic(RudeEditKind.Delete, "Dim b As Object", DeletedSymbolDisplay(FeaturesResources.field, "c"))) End Sub @@ -3665,7 +5548,7 @@ End Class "Delete [As Action]@16") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.field)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.field, "a"))) End Sub @@ -3683,7 +5566,7 @@ End Class "Delete [As Action]@18") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.event_)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.event_, "a"))) End Sub @@ -3735,6 +5618,22 @@ End Class Diagnostic(RudeEditKind.Move, "Event c", FeaturesResources.event_)) End Sub + + Public Sub EventField_Partial_InsertDelete() + Dim srcA1 = "Partial Class C : End Class" + Dim srcB1 = "Partial Class C" & vbCrLf & "Event E As System.Action : End Class" + + Dim srcA2 = "Partial Class C" & vbCrLf & "Event E As System.Action : End Class" + Dim srcB2 = "Partial Class C : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) + End Sub + Public Sub FieldInsert1() Dim src1 = "Class C : End Class" @@ -3751,7 +5650,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "WithEvents F As C", VBFeaturesResources.WithEvents_field)) + Diagnostic(RudeEditKind.InsertVirtual, "F As C", VBFeaturesResources.WithEvents_field)) End Sub @@ -3761,7 +5660,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "G", VBFeaturesResources.WithEvents_field)) + Diagnostic(RudeEditKind.InsertVirtual, "G", VBFeaturesResources.WithEvents_field)) End Sub @@ -3771,25 +5670,25 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "G", VBFeaturesResources.WithEvents_field)) + Diagnostic(RudeEditKind.InsertVirtual, "G As C", VBFeaturesResources.WithEvents_field)) End Sub Public Sub FieldInsert_IntoStruct() Dim src1 = "Structure S : Private a As Integer : End Structure" - Dim src2 = + Dim src2 = " Structure S Private a As Integer Private b As Integer Private Shared c As Integer Private Event d As System.Action End Structure -.Value +" Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.InsertIntoStruct, "Private Event d As System.Action", FeaturesResources.event_, VBFeaturesResources.structure_), - Diagnostic(RudeEditKind.InsertIntoStruct, "b", FeaturesResources.field, VBFeaturesResources.structure_), - Diagnostic(RudeEditKind.InsertIntoStruct, "c", FeaturesResources.field, VBFeaturesResources.structure_)) + Diagnostic(RudeEditKind.InsertIntoStruct, "b As Integer", FeaturesResources.field, VBFeaturesResources.structure_), + Diagnostic(RudeEditKind.InsertIntoStruct, "c As Integer", FeaturesResources.field, VBFeaturesResources.structure_)) End Sub @@ -3852,9 +5751,9 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "b", FeaturesResources.field, FeaturesResources.class_), - Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "c", FeaturesResources.field, FeaturesResources.class_), - Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "d", FeaturesResources.field, FeaturesResources.class_)) + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "b As Integer", FeaturesResources.field, FeaturesResources.class_), + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "c As Integer", FeaturesResources.field, FeaturesResources.class_), + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "d As Integer", FeaturesResources.field, FeaturesResources.class_)) End Sub @@ -3882,9 +5781,71 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "b", FeaturesResources.field, FeaturesResources.class_), - Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "c", FeaturesResources.field, FeaturesResources.class_), - Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "d", FeaturesResources.field, FeaturesResources.class_)) + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "b As Integer", FeaturesResources.field, FeaturesResources.class_), + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "c As Integer", FeaturesResources.field, FeaturesResources.class_), + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "d As Integer", FeaturesResources.field, FeaturesResources.class_)) + End Sub + + + Public Sub Field_DeleteInsert_LayoutClass_Sequential_OrderPreserved() + Dim src1 = " +Imports System.Runtime.InteropServices + + +Partial Class C + Private a As Integer + Private b As Integer +End Class +" + + Dim src2 = " +Imports System.Runtime.InteropServices + + +Partial Class C + Private a As Integer +End Class + +Partial Class C + Private b As Integer +End Class +" + + Dim edits = GetTopEdits(src1, src2) + + ' TODO: We don't compare the ordering currently. We could allow this edit if the ordering is preserved. + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "b As Integer", FeaturesResources.field, FeaturesResources.class_)) + End Sub + + + Public Sub Field_DeleteInsert_LayoutClass_Sequential_OrderChanged() + Dim src1 = " +Imports System.Runtime.InteropServices + + +Partial Class C + Private a As Integer + Private b As Integer +End Class +" + + Dim src2 = " +Imports System.Runtime.InteropServices + + +Partial Class C + Private b As Integer +End Class + +Partial Class C + Private a As Integer +End Class +" + + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "a As Integer", FeaturesResources.field, FeaturesResources.class_)) End Sub @@ -3930,7 +5891,7 @@ End Class End Sub - Public Sub FieldInsert_ParameterlessConstructorInsert_WithInitializersAndLambdas1() + Public Sub FieldInsert_ConstructorReplacingImplicitConstructor_WithInitializersAndLambdas() Dim src1 = " Imports System @@ -3954,7 +5915,7 @@ Class C Dim B As Integer = F(Function(b) b + 1) ' new field - Sub New() ' new ctor + Sub New() ' new ctor replacing existing implicit constructor F(Function(c) c + 1) End Sub End Class @@ -3969,6 +5930,53 @@ End Class SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(), syntaxMap(0))}) End Sub + + Public Sub FieldInsert_ParameterlessConstructorInsert_WithInitializersAndLambdas() + Dim src1 = " +Imports System + +Class C + Shared Function F(x As Func(Of Integer, Integer)) As Integer + Return 1 + End Function + + Dim A = F(Function(a) a + 1) + + Public Sub New(x As Integer) + End Sub +End Class +" + Dim src2 = " +Imports System + +Class C + Shared Function F(x As Func(Of Integer, Integer)) As Integer + Return 1 + End Function + + Dim A = F(Function(a) a + 1) + + Public Sub New(x As Integer) + End Sub + + Public Sub New ' new ctor + F(Function(c) c + 1) + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, "Public Sub New")) + + ' TODO (bug https//github.com/dotnet/roslyn/issues/2504): + ' edits.VerifySemantics( + ' ActiveStatementsDescription.Empty, + ' { + ' SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C").Constructors.Single(), syntaxMap(0)) + ' }) + End Sub + Public Sub FieldInsert_ConstructorInsert_WithInitializersAndLambdas1() Dim src1 = " @@ -4002,17 +6010,15 @@ End Class Dim edits = GetTopEdits(src1, src2) Dim syntaxMap = GetSyntaxMap(src1, src2) - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.B")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single())}) + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, "Sub New(x As Integer)")) - ' TODO (bug https//github.com/dotnet/roslyn/issues/2504): + ' TODO (bug https://github.com/dotnet/roslyn/issues/2504): 'edits.VerifySemantics( ' ActiveStatementsDescription.Empty, ' { - ' SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.B")), - ' SemanticEdit(SemanticEditKind.Insert, c => c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(), syntaxMap(0)) + ' SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.B")), + ' SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(), syntaxMap(0)) ' }) End Sub @@ -4114,6 +6120,27 @@ End Class Diagnostic(RudeEditKind.TypeUpdate, "Property P", FeaturesResources.auto_property)) End Sub + + Public Sub PropertyInsert() + Dim src1 = "Class C : End Class" + Dim src2 = " +Class C + Property P + Get + Return 1 + End Get + Set(value) + End Set + End Property +End Class +" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifySemantics( + semanticEdits:={SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("P"))}) + End Sub + Public Sub PrivatePropertyAccessorAddSetter() Dim src1 = "Class C : Private _p As Integer : Private ReadOnly Property P As Integer" & vbLf & "Get" & vbLf & "Return 1 : End Get : End Property : End Class" @@ -4162,7 +6189,7 @@ End Class "Delete [As Integer]@97") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Private ReadOnly Property P", VBFeaturesResources.property_accessor)) + Diagnostic(RudeEditKind.Delete, "Private ReadOnly Property P", DeletedSymbolDisplay(VBFeaturesResources.property_accessor, "P(Integer)"))) End Sub @@ -4248,29 +6275,133 @@ Class C Private Property d As Integer Get - Return 0 + Return 0 + End Get + + Set + End Set + End Property + + Private Shared Property e As Integer + Get + Return 0 + End Get + + Set + End Set + End Property +End Class +" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "Private Property b As Integer", FeaturesResources.auto_property, FeaturesResources.class_), + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "Private Shared Property c As Integer", FeaturesResources.auto_property, FeaturesResources.class_)) + End Sub + + + Public Sub Property_Partial_InsertDelete() + Dim srcA1 = "Partial Class C : End Class" + Dim srcB1 = " +Partial Class C + Private Property P As Integer + Get + Return 1 + End Get + Set + End Set + End Property +End Class +" + + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of PropertySymbol)("P").GetMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of PropertySymbol)("P").SetMethod) + }), + DocumentResults() + }) + End Sub + + + Public Sub AutoProperty_Partial_InsertDelete() + Dim srcA1 = "Partial Class C : End Class" + Dim srcB1 = " +Partial Class C + Private Property P As Integer +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) + End Sub + + + Public Sub AutoPropertyWithInitializer_Partial_InsertDelete() + Dim srcA1 = "Partial Class C : End Class" + Dim srcB1 = " +Partial Class C + Private Property P As Integer = 1 +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}), + DocumentResults() + }) + End Sub + + + Public Sub Property_Update_LiftedParameter() + Dim src1 = " +Imports System + +Partial Class C + Private Property P(a As Integer) As Integer + Get + Return New Func(Of Integer)(Function() a + 1).Invoke() End Get - Set End Set End Property +End Class +" + Dim src2 = " +Imports System - Private Shared Property e As Integer +Partial Class C + Private Property P(a As Integer) As Integer Get - Return 0 + Return New Func(Of Integer)(Function() 2).Invoke() End Get - Set End Set End Property End Class " + Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "Private Property b As Integer", FeaturesResources.auto_property, FeaturesResources.class_), - Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "Private Shared Property c As Integer", FeaturesResources.auto_property, FeaturesResources.class_)) - End Sub + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.NotCapturingVariable, "a", "a")) + End Sub #End Region #Region "Field And Property Initializers" @@ -4339,6 +6470,56 @@ End Class {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub + ''' + ''' It's a semantic error to specify array bunds and initializer at the same time. + ''' EnC analysis needs to handle this case without failing. + ''' + + Public Sub Field_InitializerUpdate_InitializerAndArrayBounds() + Dim src1 = " +Class C + Dim x(1) As Integer = 1 +End Class +" + + Dim src2 = " +Class C + Dim x(2) As Integer = 2 +End Class +" + + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + End Sub + + ''' + ''' It's a semantic error to specify array bunds and initializer at the same time. + ''' EnC analysis needs to handle this case without failing. + ''' + + Public Sub Field_InitializerUpdate_AsNewAndArrayBounds() + Dim src1 = " +Class C + Dim x(1) As New C +End Class +" + + Dim src2 = " +Class C + Dim x(2) As New C +End Class +" + + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + End Sub + Public Sub Field_InitializerUpdate_AsNew2() Dim src1 = "Class C : Dim a, b As New Decimal(1) : End Class" @@ -4471,7 +6652,7 @@ End Class "Delete [()]@47") edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -4487,7 +6668,7 @@ End Class "Delete [()]@51") edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -4503,7 +6684,7 @@ End Class "Delete [()]@38") edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -4519,7 +6700,7 @@ End Class "Delete [()]@56") edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -4535,7 +6716,7 @@ End Class "Delete [()]@43") edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -4545,7 +6726,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.constructor)) + Diagnostic(RudeEditKind.ChangingVisibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) End Sub @@ -4555,7 +6736,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.constructor)) + Diagnostic(RudeEditKind.ChangingVisibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) End Sub @@ -4565,7 +6746,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -4575,7 +6756,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -4948,8 +7129,10 @@ End Class Dim src2 = "Partial Class C : Dim a = 2 : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.PartialTypeInitializerUpdate, "a = 2", FeaturesResources.field)) + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single, preserveLocalVariables:=True) + }) End Sub @@ -4958,8 +7141,10 @@ End Class Dim src2 = "Partial Class C : Property a = 2 : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.PartialTypeInitializerUpdate, "Property a = 2", FeaturesResources.auto_property)) + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single, preserveLocalVariables:=True) + }) End Sub @@ -4968,8 +7153,10 @@ End Class Dim src2 = "Partial Class C : Dim a = 2 : End Class : Class C : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.PartialTypeInitializerUpdate, "a = 2", FeaturesResources.field)) + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single, preserveLocalVariables:=True) + }) End Sub @@ -4978,8 +7165,10 @@ End Class Dim src2 = "Partial Class C : Property a = 2 : End Class : Class C : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.PartialTypeInitializerUpdate, "Property a = 2", FeaturesResources.auto_property)) + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single, preserveLocalVariables:=True) + }) End Sub @@ -4988,8 +7177,10 @@ End Class Dim src2 = "Class C : Dim a = 2 : End Class : Partial Class C : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.PartialTypeInitializerUpdate, "a = 2", FeaturesResources.field)) + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single, preserveLocalVariables:=True) + }) End Sub @@ -4998,29 +7189,53 @@ End Class Dim src2 = "Class C : Property a = 2 : End Class : Partial Class C : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.PartialTypeInitializerUpdate, "Property a = 2", FeaturesResources.auto_property)) + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single, preserveLocalVariables:=True) + }) End Sub - - Public Sub PrivateFieldInsert1() + + + + + + + + + Public Sub Field_Insert(modifiers As String) Dim src1 = "Class C : End Class" - Dim src2 = "Class C : Private a As Integer = 1 : End Class" + Dim src2 = "Class C : " & modifiers & " a As Integer = 1 : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Insert [Private a As Integer = 1]@10", - "Insert [a As Integer = 1]@18", - "Insert [a]@18", - "Insert [As Integer]@20") + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("a")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + End Sub - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.a")), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + + + + + + + + Public Sub Property_Insert(accessibility As String) + Dim src1 = "Class C : End Class" + Dim src2 = "Class C : " & accessibility & " Property a As Integer = 1 : End Class" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("a")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) End Sub - - Public Sub PrivateFieldInsert2() + + Public Sub Field_Insert_MultiDeclaration() Dim src1 = "Class C : Private a As Integer = 1 : End Class" Dim src2 = "Class C : Private a, b As Integer : End Class" Dim edits = GetTopEdits(src1, src2) @@ -5035,101 +7250,135 @@ End Class End Sub - Public Sub PrivatePropertyInsert() - Dim src1 = "Class C : End Class" - Dim src2 = "Class C : Private Property a As Integer = 1 : End Class" + Public Sub Field_Insert_MultiDeclaration_AsNew() + Dim src1 = "Class C : Private a As C = New C : End Class" + Dim src2 = "Class C : Private a, b As New C : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Insert [Private Property a As Integer = 1]@10", - "Insert [As Integer]@29") + "Update [a As C = New C]@18 -> [a, b As New C]@18", + "Insert [b]@21", + "Delete [As C]@20") - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.a")), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + ' TODO: allow this edit + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "a, b As New C", VBFeaturesResources.as_clause)) End Sub - Public Sub PrivateFieldInsert_Untyped() - Dim src1 = "Class C : End Class" - Dim src2 = "Class C : Private a = 1 : End Class" + Public Sub Field_Insert_MultiDeclaration_Split() + Dim src1 = "Class C : Private a, b As Integer = 1 : End Class" + Dim src2 = "Class C : Private a As Integer : Private b As Integer : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Insert [Private a = 1]@10", - "Insert [a = 1]@18", - "Insert [a]@18") + "Insert [Private b As Integer]@33", + "Update [a, b As Integer = 1]@18 -> [a As Integer]@18", + "Insert [b As Integer]@41", + "Move [b]@21 -> @41", + "Insert [As Integer]@43") - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.a")), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) End Sub - Public Sub PrivatePropertyInsert_Untyped() - Dim src1 = "Class C : End Class" - Dim src2 = "Class C : Private Property a = 1 : End Class" + Public Sub Field_DeleteInsert_MultiDeclaration_TypeChange() + Dim src1 = "Class C : Private a, b As Integer = 1 : End Class" + Dim src2 = "Class C : Private a As Integer : Private b As Byte : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Insert [Private Property a = 1]@10") - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.a")), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.TypeUpdate, "b As Byte", FeaturesResources.field)) End Sub - Public Sub PrivateReadOnlyFieldInsert() - Dim src1 = "Class C : End Class" - Dim src2 = "Class C : Private ReadOnly a As Integer = 1 : End Class" - Dim edits = GetTopEdits(src1, src2) + Public Sub Field_DeleteInsert_Partial_MultiDeclaration_TypeChange() + Dim srcA1 = "Partial Class C : Private a As Integer = 1 : End Class" + Dim srcB1 = "Partial Class C : End Class" - edits.VerifyEdits( - "Insert [Private ReadOnly a As Integer = 1]@10", - "Insert [a As Integer = 1]@27", - "Insert [a]@27", - "Insert [As Integer]@29") + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "Partial Class C : Private a, b As Byte : End Class" - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.a")), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(diagnostics:= + { + Diagnostic(RudeEditKind.TypeUpdate, "a, b As Byte", FeaturesResources.field) + }) + }) End Sub - Public Sub PublicFieldInsert() - Dim src1 = "Class C : End Class" - Dim src2 = "Class C : Public a As Integer = 1 : End Class" - Dim edits = GetTopEdits(src1, src2) + Public Sub Field_DeleteInsert_MultiDeclaration_Split() + Dim srcA1 = "Partial Class C : Private a, b As Integer : End Class" + Dim srcB1 = "Partial Class C : End Class" - edits.VerifyRudeDiagnostics() + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "Partial Class C : Private a As Integer = 1 : Private b As Integer = 2 : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + }) End Sub - Public Sub PublicPropertyInsert() - Dim src1 = "Class C : End Class" - Dim src2 = "Class C : Property a As Integer = 1 : End Class" - Dim edits = GetTopEdits(src1, src2) + Public Sub Field_DeleteInsert_Partial_MultiDeclaration_UpdateArrayBounds() + Dim srcA1 = "Partial Class C : Dim F1(1, 2), F2? As Integer : End Class" + Dim srcB1 = "Partial Class C : End Class" - edits.VerifyRudeDiagnostics() + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = "Partial Class C : Dim F1(1, 3), F2? As Integer : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + }) End Sub - Public Sub ProtectedFieldInsert() + Public Sub FieldInsert_PrivateUntyped() Dim src1 = "Class C : End Class" - Dim src2 = "Class C : Protected a As Integer = 1 : End Class" + Dim src2 = "Class C : Private a = 1 : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyRudeDiagnostics() + edits.VerifyEdits( + "Insert [Private a = 1]@10", + "Insert [a = 1]@18", + "Insert [a]@18") + + edits.VerifySemantics(ActiveStatementsDescription.Empty, + {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.a")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub - Public Sub ProtectedPropertyInsert() + Public Sub PropertyInsert_PrivateUntyped() Dim src1 = "Class C : End Class" - Dim src2 = "Class C : Protected Property a As Integer = 1 : End Class" + Dim src2 = "Class C : Private Property a = 1 : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyRudeDiagnostics() + edits.VerifyEdits( + "Insert [Private Property a = 1]@10") + + edits.VerifySemantics(ActiveStatementsDescription.Empty, + {SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.a")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -5145,7 +7394,7 @@ End Class "Delete [As Integer]@24") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.field)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.field, "a"))) End Sub @@ -5159,7 +7408,7 @@ End Class "Delete [As Integer]@29") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", FeaturesResources.auto_property)) + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.auto_property, "a"))) End Sub @@ -5873,13 +8122,59 @@ End Class {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), syntaxMap(0))}) End Sub + + Public Sub FieldInitializerUpdate_Lambdas_PartialDeclarationDelete_SingleDocument() + Dim src1 = " +Partial Class C + Dim x = F(Function(a) a + 1) +End Class + +Partial Class C + Dim y = F(Function(a) a + 10) +End Class + +Partial Class C + Public Sub New() + End Sub + + Shared Function F(x As Func(Of Integer, Integer)) + Return 1 + End Function +End Class +" + + Dim src2 = " +Partial Class C + Dim x = F(Function(a) a + 1) +End Class + +Partial Class C + Dim y = F(Function(a) a + 10) + + Shared Function F(x As Func(Of Integer, Integer)) + Return 1 + End Function +End Class +" + Dim edits = GetTopEdits(src1, src2) + + Dim syntaxMap = GetSyntaxMap(src1, src2) + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("F"), partialType:="C"), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), syntaxMap(0), partialType:="C") + }) + End Sub + Public Sub FieldInitializerUpdate_ActiveStatements1() Dim src1 As String = " Imports System Class C - Dim A As Integer = 1 + Dim A As Integer = 1 Dim B As Integer = 1 Public Sub New(a As Integer) @@ -5895,7 +8190,7 @@ End Class Imports System Class C - Dim A As Integer = 1 + Dim A As Integer = 1 Dim B As Integer = 2 Public Sub New(a As Integer) @@ -5909,12 +8204,12 @@ End Class" Dim edits = GetTopEdits(src1, src2) Dim syntaxMap = GetSyntaxMap(src1, src2) - Dim activeStatements = GetActiveStatements(src1, src2) - edits.VerifySemantics( - activeStatements, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors(0), syntaxMap(0)), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors(1), syntaxMap(0))}) + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors(0), syntaxMap(0)), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors(1), syntaxMap(0)) + }) End Sub @@ -5957,10 +8252,10 @@ Partial Class C End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifySemantics(ActiveStatementsDescription.Empty, expectedSemanticEdits:= + edits.VerifySemantics(ActiveStatementsDescription.Empty, semanticEdits:= { - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True), - SemanticEdit(SemanticEditKind.Update, Function(c) CType(c.GetMember(Of NamedTypeSymbol)("C").GetMembers("P").Skip(1).First(), IPropertySymbol).GetMethod) + SemanticEdit(SemanticEditKind.Update, Function(c) CType(c.GetMember(Of NamedTypeSymbol)("C").GetMembers("P").First(), IPropertySymbol).GetMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) }) End Sub @@ -6068,7 +8363,7 @@ Imports System.Runtime.InteropServices Class C - Private Custom Event c As Action + Private Custom Event E As Action AddHandler(value As Action) End AddHandler @@ -6081,7 +8376,71 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics() + edits.VerifySemantics( + semanticEdits:={SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("E"))}) + End Sub + + + Public Sub Event_Delete() + Dim src1 = " +Imports System +Imports System.Runtime.InteropServices + +Class C + Private Custom Event E As Action + AddHandler(value As Action) + End AddHandler + + RemoveHandler(value As Action) + End RemoveHandler + + RaiseEvent() + End RaiseEvent + End Event +End Class +" + Dim src2 = " +Imports System +Imports System.Runtime.InteropServices + +Class C +End Class +" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.event_, "E"))) + End Sub + + + Public Sub Event_Partial_InsertDelete() + Dim srcA1 = "Partial Class C : End Class" + Dim srcB1 = " +Partial Class C + Custom Event E As EventHandler + AddHandler(value As EventHandler) + End AddHandler + RemoveHandler(value As EventHandler) + End RemoveHandler + RaiseEvent(sender As Object, e As EventArgs) + End RaiseEvent + End Event +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of EventSymbol)("E").AddMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of EventSymbol)("E").RemoveMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of EventSymbol)("E").RaiseMethod) + }), + DocumentResults() + }) End Sub #End Region @@ -6536,8 +8895,8 @@ End Class Public Sub MethodTypeParameterInsert1() - Dim src1 = "Class C : " & vbLf & "Public Sub M() : End Sub : End Class" - Dim src2 = "Class C : " & vbLf & "Public Sub M(Of A)() : End Sub : End Class" + Dim src1 = "Class C : " & vbLf & "Public Sub M()" & vbLf & "End Sub : End Class" + Dim src2 = "Class C : " & vbLf & "Public Sub M(Of A)()" & vbLf & "End Sub : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( @@ -6550,8 +8909,8 @@ End Class Public Sub MethodTypeParameterInsert2() - Dim src1 = "Class C : " & vbLf & "Public Sub M(Of A)() : End Sub : End Class" - Dim src2 = "Class C : " & vbLf & "Public Sub M(Of A, B)() : End Sub : End Class" + Dim src1 = "Class C : " & vbLf & "Public Sub M(Of A)()" & vbLf & "End Sub : End Class" + Dim src2 = "Class C : " & vbLf & "Public Sub M(Of A, B)()" & vbLf & "End Sub : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( @@ -6578,8 +8937,8 @@ End Class Public Sub MethodTypeParameterDelete2() - Dim src1 = "Class C : " & vbLf & "Public Sub M(Of A, B)() : End Sub : End Class" - Dim src2 = "Class C : " & vbLf & "Public Sub M(Of B)() : End Sub : End Class" + Dim src1 = "Class C : " & vbLf & "Public Sub M(Of A, B)()" & vbLf & "End Sub : End Class" + Dim src2 = "Class C : " & vbLf & "Public Sub M(Of B)()" & vbLf & "End Sub : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb index 0bb91b616e648..84fb2799371c4 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb @@ -473,8 +473,8 @@ End Class Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray(Of TextSpan).Empty, CancellationToken.None) Assert.True(result.HasChanges) - Assert.True(result.SemanticEdits(0).PreserveLocalVariables) Dim syntaxMap = result.SemanticEdits(0).SyntaxMap + Assert.NotNull(syntaxMap) Dim newStatementSpan = result.ActiveStatements(0).Span Dim newStatementTextSpan = newText.Lines.GetTextSpan(newStatementSpan) @@ -662,20 +662,21 @@ End Class End Sub - Public Async Function AnalyzeDocumentAsync_Adding_A_New_File() As Task + Public Async Function AnalyzeDocumentAsync_AddingNewFile() As Task Dim source1 = " -Class C - Public Shared Sub Main() - End Sub -End Class +Namespace N + Class C + Public Shared Sub Main() + End Sub + End Class +End Namespace " Dim source2 = " -Private Class D +Class D End Class " Using workspace = TestWorkspace.CreateVisualBasic(source1, composition:=s_composition) - ' fork the solution to introduce a change Dim oldSolution = workspace.CurrentSolution Dim oldProject = oldSolution.Projects.Single() Dim newDocId = DocumentId.CreateNewId(oldProject.Id) @@ -692,16 +693,15 @@ End Class Dim changedDocuments = changes.GetChangedDocuments().Concat(changes.GetAddedDocuments()) - Dim analyzer = New VisualBasicEditAndContinueAnalyzer() Dim result = New List(Of DocumentAnalysisResults)() Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() + Dim analyzer = New VisualBasicEditAndContinueAnalyzer() For Each changedDocumentId In changedDocuments result.Add(Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None)) Next Assert.True(result.IsSingle()) - Assert.Equal(1, result.Single().RudeEditErrors.Count()) - Assert.Equal(RudeEditKind.InsertFile, result.Single().RudeEditErrors.Single().Kind) + Assert.Empty(result.Single().RudeEditErrors) End Using End Function End Class diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/Microsoft.CodeAnalysis.CSharp.ExpressionCompiler.UnitTests.csproj b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/Microsoft.CodeAnalysis.CSharp.ExpressionCompiler.UnitTests.csproj index 44580297f2bcc..185a531feed87 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/Microsoft.CodeAnalysis.CSharp.ExpressionCompiler.UnitTests.csproj +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/Microsoft.CodeAnalysis.CSharp.ExpressionCompiler.UnitTests.csproj @@ -30,5 +30,5 @@ - + \ No newline at end of file diff --git a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj index 077b60fd24523..2a2309cb7b90e 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj +++ b/src/ExpressionEvaluator/CSharp/Test/ResultProvider/Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj @@ -32,5 +32,5 @@ - + \ No newline at end of file diff --git a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Microsoft.CodeAnalysis.FunctionResolver.UnitTests.csproj b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Microsoft.CodeAnalysis.FunctionResolver.UnitTests.csproj index 16ddfbcb98932..89d587c4f25c1 100644 --- a/src/ExpressionEvaluator/Core/Test/FunctionResolver/Microsoft.CodeAnalysis.FunctionResolver.UnitTests.csproj +++ b/src/ExpressionEvaluator/Core/Test/FunctionResolver/Microsoft.CodeAnalysis.FunctionResolver.UnitTests.csproj @@ -26,5 +26,5 @@ - + \ No newline at end of file diff --git a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/Microsoft.CodeAnalysis.VisualBasic.ExpressionCompiler.UnitTests.vbproj b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/Microsoft.CodeAnalysis.VisualBasic.ExpressionCompiler.UnitTests.vbproj index 1d56e4d4c94da..24990afab6887 100644 --- a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/Microsoft.CodeAnalysis.VisualBasic.ExpressionCompiler.UnitTests.vbproj +++ b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/Microsoft.CodeAnalysis.VisualBasic.ExpressionCompiler.UnitTests.vbproj @@ -34,5 +34,5 @@ - + \ No newline at end of file diff --git a/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/Microsoft.CodeAnalysis.VisualBasic.ResultProvider.UnitTests.vbproj b/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/Microsoft.CodeAnalysis.VisualBasic.ResultProvider.UnitTests.vbproj index 6bde6cecbbd52..d7d9ee3090407 100644 --- a/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/Microsoft.CodeAnalysis.VisualBasic.ResultProvider.UnitTests.vbproj +++ b/src/ExpressionEvaluator/VisualBasic/Test/ResultProvider/Microsoft.CodeAnalysis.VisualBasic.ResultProvider.UnitTests.vbproj @@ -35,5 +35,5 @@ - + \ No newline at end of file diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx index 53401ede16e59..4938de097aeef 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx @@ -401,8 +401,8 @@ global statement {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace + + extern alias using directive diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ExtensionMethodImportCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ExtensionMethodImportCompletionProvider.cs index 02e8cc228aba9..da74b72eff123 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ExtensionMethodImportCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ExtensionMethodImportCompletionProvider.cs @@ -44,8 +44,8 @@ protected override ImmutableArray GetImportedNamespaces( CancellationToken cancellationToken) => ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel); - protected override Task CreateContextAsync(Document document, int position, CancellationToken cancellationToken) - => ImportCompletionProviderHelper.CreateContextAsync(document, position, cancellationToken); + protected override Task CreateContextAsync(Document document, int position, bool usePartialSemantic, CancellationToken cancellationToken) + => ImportCompletionProviderHelper.CreateContextAsync(document, position, usePartialSemantic, cancellationToken); protected override bool IsFinalSemicolonOfUsingOrExtern(SyntaxNode directive, SyntaxToken token) { diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ImportCompletionProviderHelper.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ImportCompletionProviderHelper.cs index 10208430c8253..5115b26714e29 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ImportCompletionProviderHelper.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/ImportCompletionProviderHelper.cs @@ -21,11 +21,15 @@ public static ImmutableArray GetImportedNamespaces( => semanticModel.GetUsingNamespacesInScope(location) .SelectAsArray(namespaceSymbol => namespaceSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat)); - public static async Task CreateContextAsync(Document document, int position, CancellationToken cancellationToken) + public static async Task CreateContextAsync(Document document, int position, bool usePartialSemantic, CancellationToken cancellationToken) { // Need regular semantic model because we will use it to get imported namespace symbols. Otherwise we will try to // reach outside of the span and ended up with "node not within syntax tree" error from the speculative model. - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + // Also we use partial model unless full model is explictly request (e.g. in tests) so that we don't have to wait for all semantics to be computed. + var semanticModel = usePartialSemantic + ? await document.GetPartialSemanticModelAsync(cancellationToken).ConfigureAwait(false) + : await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(semanticModel); return CSharpSyntaxContext.CreateContext(document.Project.Solution.Workspace, semanticModel, position, cancellationToken); } } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/TypeImportCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/TypeImportCompletionProvider.cs index 6d7dd6cc3a0e4..59e6118414cbd 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/TypeImportCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/ImportCompletion/TypeImportCompletionProvider.cs @@ -45,8 +45,8 @@ protected override ImmutableArray GetImportedNamespaces( CancellationToken cancellationToken) => ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel); - protected override Task CreateContextAsync(Document document, int position, CancellationToken cancellationToken) - => ImportCompletionProviderHelper.CreateContextAsync(document, position, cancellationToken); + protected override Task CreateContextAsync(Document document, int position, bool usePartialSemantic, CancellationToken cancellationToken) + => ImportCompletionProviderHelper.CreateContextAsync(document, position, usePartialSemantic, cancellationToken); protected override bool IsFinalSemicolonOfUsingOrExtern(SyntaxNode directive, SyntaxToken token) { diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index b6cef0d5793da..4d37f47d11f81 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -90,6 +90,7 @@ private enum SwitchExpressionPart case SyntaxKind.ConversionOperatorDeclaration: case SyntaxKind.OperatorDeclaration: case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: case SyntaxKind.GetAccessorDeclaration: @@ -130,14 +131,14 @@ private enum SwitchExpressionPart } /// - /// Given a node representing a declaration ( = true) or a top-level match node ( = false) returns: + /// Given a node representing a declaration or a top-level match node returns: /// - for method-like member declarations with block bodies (methods, operators, constructors, destructors, accessors). /// - for variable declarators of fields, properties with an initializer expression, or /// for method-like member declarations with expression bodies (methods, properties, indexers, operators) /// /// A null reference otherwise. /// - internal override SyntaxNode? TryGetDeclarationBody(SyntaxNode node, bool isMember) + internal override SyntaxNode? TryGetDeclarationBody(SyntaxNode node) { if (node.IsKind(SyntaxKind.VariableDeclarator, out VariableDeclaratorSyntax? variableDeclarator)) { @@ -493,10 +494,10 @@ internal override SyntaxNode FindPartner(SyntaxNode leftRoot, SyntaxNode rightRo if (leftEqualsClause.Parent.IsKind(SyntaxKind.PropertyDeclaration, out PropertyDeclarationSyntax? leftDeclaration)) { var leftSymbol = leftModel.GetDeclaredSymbol(leftDeclaration, cancellationToken); - RoslynDebug.Assert(leftSymbol != null); + Contract.ThrowIfNull(leftSymbol); var rightProperty = rightType.GetMembers(leftSymbol.Name).Single(); - var rightDeclaration = (PropertyDeclarationSyntax)GetSymbolSyntax(rightProperty, cancellationToken); + var rightDeclaration = (PropertyDeclarationSyntax)rightProperty.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); rightEqualsClause = rightDeclaration.Initializer; } @@ -504,10 +505,10 @@ internal override SyntaxNode FindPartner(SyntaxNode leftRoot, SyntaxNode rightRo { var leftDeclarator = (VariableDeclaratorSyntax)leftEqualsClause.Parent!; var leftSymbol = leftModel.GetDeclaredSymbol(leftDeclarator, cancellationToken); - RoslynDebug.Assert(leftSymbol != null); + Contract.ThrowIfNull(leftSymbol); var rightField = rightType.GetMembers(leftSymbol.Name).Single(); - var rightDeclarator = (VariableDeclaratorSyntax)GetSymbolSyntax(rightField, cancellationToken); + var rightDeclarator = (VariableDeclaratorSyntax)rightField.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); rightEqualsClause = rightDeclarator.Initializer; } @@ -550,6 +551,14 @@ protected override IEnumerable GetLambdaBodyExpressionsAndStatements protected override Match ComputeTopLevelMatch(SyntaxNode oldCompilationUnit, SyntaxNode newCompilationUnit) => SyntaxComparer.TopLevel.ComputeMatch(oldCompilationUnit, newCompilationUnit); + protected override Match ComputeTopLevelDeclarationMatch(SyntaxNode oldDeclaration, SyntaxNode newDeclaration) + { + Contract.ThrowIfNull(oldDeclaration.Parent); + Contract.ThrowIfNull(newDeclaration.Parent); + var comparer = new SyntaxComparer(oldDeclaration.Parent, newDeclaration.Parent, new[] { oldDeclaration }, new[] { newDeclaration }); + return comparer.ComputeMatch(oldDeclaration.Parent, newDeclaration.Parent); + } + protected override Match ComputeBodyMatch(SyntaxNode oldBody, SyntaxNode newBody, IEnumerable>? knownMatches) { SyntaxUtilities.AssertIsBody(oldBody, allowLambda: true); @@ -1020,26 +1029,17 @@ internal override bool IsInterfaceDeclaration(SyntaxNode node) => node.IsKind(SyntaxKind.InterfaceDeclaration); internal override SyntaxNode? TryGetContainingTypeDeclaration(SyntaxNode node) - => node.Parent!.FirstAncestorOrSelf(); + => node.Parent!.FirstAncestorOrSelf(); internal override bool HasBackingField(SyntaxNode propertyOrIndexerDeclaration) => propertyOrIndexerDeclaration.IsKind(SyntaxKind.PropertyDeclaration, out PropertyDeclarationSyntax? propertyDecl) && SyntaxUtilities.HasBackingField(propertyDecl); - internal override bool IsDeclarationWithInitializer(SyntaxNode declaration) - { - switch (declaration.Kind()) - { - case SyntaxKind.VariableDeclarator: - return ((VariableDeclaratorSyntax)declaration).Initializer != null; + internal override SyntaxNode? TryGetAssociatedMemberDeclaration(SyntaxNode node) + => node.Parent.IsParentKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration, SyntaxKind.EventDeclaration) ? node.Parent.Parent : null; - case SyntaxKind.PropertyDeclaration: - return ((PropertyDeclarationSyntax)declaration).Initializer != null; - - default: - return false; - } - } + internal override bool IsDeclarationWithInitializer(SyntaxNode declaration) + => declaration is VariableDeclaratorSyntax { Initializer: not null } || declaration is PropertyDeclarationSyntax { Initializer: not null }; internal override bool IsConstructorWithMemberInitializers(SyntaxNode constructorDeclaration) => constructorDeclaration is ConstructorDeclarationSyntax ctor && (ctor.Initializer == null || ctor.Initializer.IsKind(SyntaxKind.BaseConstructorInitializer)); @@ -1048,12 +1048,23 @@ internal override bool IsPartial(INamedTypeSymbol type) { var syntaxRefs = type.DeclaringSyntaxReferences; return syntaxRefs.Length > 1 - || ((TypeDeclarationSyntax)syntaxRefs.Single().GetSyntax()).Modifiers.Any(SyntaxKind.PartialKeyword); + || ((BaseTypeDeclarationSyntax)syntaxRefs.Single().GetSyntax()).Modifiers.Any(SyntaxKind.PartialKeyword); } - protected override ISymbol? GetSymbolForEdit(SemanticModel model, SyntaxNode node, EditKind editKind, IReadOnlyDictionary editMap, CancellationToken cancellationToken) + protected override SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference reference, CancellationToken cancellationToken) + => reference.GetSyntax(cancellationToken); + + protected override ISymbol? GetSymbolForEdit( + SemanticModel model, + SyntaxNode node, + EditKind editKind, + IReadOnlyDictionary editMap, + out bool isAmbiguous, + CancellationToken cancellationToken) { - if (node.IsKind(SyntaxKind.Parameter)) + isAmbiguous = false; + + if (node.IsKind(SyntaxKind.Parameter, SyntaxKind.TypeParameter, SyntaxKind.UsingDirective, SyntaxKind.NamespaceDeclaration)) { return null; } @@ -1066,7 +1077,7 @@ internal override bool IsPartial(INamedTypeSymbol type) return null; } - if (node.IsKind(SyntaxKind.IndexerDeclaration) || node.IsKind(SyntaxKind.PropertyDeclaration)) + if (node.IsKind(SyntaxKind.IndexerDeclaration, SyntaxKind.PropertyDeclaration)) { // The only legitimate update of an indexer/property declaration is an update of its expression body. // The expression body itself may have been updated, replaced with an explicit getter, or added to replace an explicit getter. @@ -1081,43 +1092,46 @@ internal override bool IsPartial(INamedTypeSymbol type) return null; } - return model.GetDeclaredSymbol(node, cancellationToken); + var symbol = model.GetDeclaredSymbol(node, cancellationToken); + + // Ignore partial method definition parts. + // Partial method that does not have implementation part is not emitted to metadata. + // Partial method without a definition part is a compilation error. + if (symbol is IMethodSymbol { IsPartialDefinition: true }) + { + return null; + } + + return symbol; } - protected override bool TryGetDeclarationBodyEdit(Edit edit, IReadOnlyDictionary editMap, out SyntaxNode? oldBody, out SyntaxNode? newBody) + protected override void GetUpdatedDeclarationBodies(SyntaxNode oldDeclaration, SyntaxNode newDeclaration, out SyntaxNode? oldBody, out SyntaxNode? newBody) { // Detect a transition between a property/indexer with an expression body and with an explicit getter. // int P => old_body; <-> int P { get { new_body } } // int this[args] => old_body; <-> int this[args] { get { new_body } } // First, return getter or expression body for property/indexer update: - if (edit.Kind == EditKind.Update && (edit.OldNode.IsKind(SyntaxKind.PropertyDeclaration) || edit.OldNode.IsKind(SyntaxKind.IndexerDeclaration))) + if (oldDeclaration.IsKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration)) { - oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(edit.OldNode); - newBody = SyntaxUtilities.TryGetEffectiveGetterBody(edit.NewNode); + oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(oldDeclaration); + newBody = SyntaxUtilities.TryGetEffectiveGetterBody(newDeclaration); if (oldBody != null && newBody != null) { - return true; + return; } } - // Second, ignore deletion of a getter body: - if (IsGetterToExpressionBodyTransformation(edit.Kind, edit.OldNode ?? edit.NewNode, editMap)) - { - oldBody = newBody = null; - return false; - } - - return base.TryGetDeclarationBodyEdit(edit, editMap, out oldBody, out newBody); + base.GetUpdatedDeclarationBodies(oldDeclaration, newDeclaration, out oldBody, out newBody); } private static bool IsGetterToExpressionBodyTransformation(EditKind editKind, SyntaxNode node, IReadOnlyDictionary editMap) { - if ((editKind == EditKind.Insert || editKind == EditKind.Delete) && node.IsKind(SyntaxKind.GetAccessorDeclaration)) + if ((editKind is EditKind.Insert or EditKind.Delete) && node.IsKind(SyntaxKind.GetAccessorDeclaration)) { RoslynDebug.Assert(node.Parent.IsKind(SyntaxKind.AccessorList)); - RoslynDebug.Assert(node.Parent.Parent.IsKind(SyntaxKind.PropertyDeclaration) || node.Parent.Parent.IsKind(SyntaxKind.IndexerDeclaration)); + RoslynDebug.Assert(node.Parent.Parent.IsKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration)); return editMap.TryGetValue(node.Parent, out var parentEdit) && parentEdit == editKind && editMap.TryGetValue(node.Parent!.Parent, out parentEdit) && parentEdit == EditKind.Update; } @@ -1135,7 +1149,7 @@ internal override bool IsLocalFunction(SyntaxNode node) => node.IsKind(SyntaxKind.LocalFunctionStatement); internal override bool IsNestedFunction(SyntaxNode node) - => node is LambdaExpressionSyntax || node is LocalFunctionStatementSyntax; + => node is AnonymousFunctionExpressionSyntax or LocalFunctionStatementSyntax; internal override bool TryGetLambdaBodies(SyntaxNode node, [NotNullWhen(true)] out SyntaxNode? body1, out SyntaxNode? body2) => LambdaUtilities.TryGetLambdaBodies(node, out body1, out body2); @@ -1364,6 +1378,7 @@ private static bool GroupBySignatureComparer(ImmutableArray ol case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: case SyntaxKind.UnknownAccessorDeclaration: @@ -1629,7 +1644,7 @@ internal override TextSpan GetLambdaParameterDiagnosticSpan(SyntaxNode lambda, i return CSharpFeaturesResources.global_statement; case SyntaxKind.ExternAliasDirective: - return CSharpFeaturesResources.using_namespace; + return CSharpFeaturesResources.extern_alias; case SyntaxKind.UsingDirective: // Dev12 distinguishes using alias from using namespace and reports different errors for removing alias. @@ -1675,7 +1690,8 @@ internal override TextSpan GetLambdaParameterDiagnosticSpan(SyntaxNode lambda, i return FeaturesResources.operator_; case SyntaxKind.ConstructorDeclaration: - return FeaturesResources.constructor; + var ctor = (ConstructorDeclarationSyntax)node; + return ctor.Modifiers.Any(SyntaxKind.StaticKeyword) ? FeaturesResources.static_constructor : FeaturesResources.constructor; case SyntaxKind.DestructorDeclaration: return CSharpFeaturesResources.destructor; @@ -1703,6 +1719,7 @@ internal override TextSpan GetLambdaParameterDiagnosticSpan(SyntaxNode lambda, i return CSharpFeaturesResources.indexer_getter; } + case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: if (node.Parent!.Parent!.IsKind(SyntaxKind.PropertyDeclaration)) { @@ -1954,7 +1971,7 @@ public void ClassifyEdit() return; case EditKind.Move: - ClassifyMove(); + ClassifyMove(_newNode!); return; case EditKind.Insert: @@ -1972,9 +1989,9 @@ public void ClassifyEdit() #region Move and Reorder - private void ClassifyMove() + private void ClassifyMove(SyntaxNode newNode) { - if (_newNode.IsKind(SyntaxKind.LocalFunctionStatement)) + if (newNode.IsKind(SyntaxKind.LocalFunctionStatement)) { return; } @@ -2016,6 +2033,7 @@ private void ClassifyReorder(SyntaxNode newNode) case SyntaxKind.EventDeclaration: case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: case SyntaxKind.TypeParameterConstraintClause: @@ -2064,89 +2082,35 @@ private void ClassifyInsert(SyntaxNode node) case SyntaxKind.ExternAliasDirective: case SyntaxKind.UsingDirective: case SyntaxKind.NamespaceDeclaration: - case SyntaxKind.DestructorDeclaration: ReportError(RudeEditKind.Insert); return; case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: - var typeDeclaration = (TypeDeclarationSyntax)node; - ClassifyTypeWithPossibleExternMembersInsert(typeDeclaration); - return; - case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: - return; - case SyntaxKind.PropertyDeclaration: - var propertyDeclaration = (PropertyDeclarationSyntax)node; - ClassifyModifiedMemberInsert(propertyDeclaration, propertyDeclaration.Modifiers); - return; - case SyntaxKind.EventDeclaration: - var eventDeclaration = (EventDeclarationSyntax)node; - ClassifyModifiedMemberInsert(eventDeclaration, eventDeclaration.Modifiers); - return; - case SyntaxKind.IndexerDeclaration: - var indexerDeclaration = (IndexerDeclarationSyntax)node; - ClassifyModifiedMemberInsert(indexerDeclaration, indexerDeclaration.Modifiers); - return; - + case SyntaxKind.DestructorDeclaration: case SyntaxKind.ConversionOperatorDeclaration: case SyntaxKind.OperatorDeclaration: - ReportError(RudeEditKind.InsertOperator); - return; - case SyntaxKind.MethodDeclaration: - ClassifyMethodInsert((MethodDeclarationSyntax)node); - return; - case SyntaxKind.ConstructorDeclaration: - // Allow adding parameterless constructor. - // Semantic analysis will determine if it's an actual addition or - // just an update of an existing implicit constructor. - var method = (BaseMethodDeclarationSyntax)node; - if (SyntaxUtilities.IsParameterlessConstructor(method)) - { - // Disallow adding an extern constructor - if (method.Modifiers.Any(SyntaxKind.ExternKeyword)) - { - ReportError(RudeEditKind.InsertExtern); - } - - return; - } - - ClassifyModifiedMemberInsert(method, method.Modifiers); - return; - case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: - ClassifyAccessorInsert((AccessorDeclarationSyntax)node); - return; - - case SyntaxKind.AccessorList: - // an error will be reported for each accessor - return; - case SyntaxKind.FieldDeclaration: case SyntaxKind.EventFieldDeclaration: - // allowed: private fields in classes - ClassifyFieldInsert((BaseFieldDeclarationSyntax)node); - return; - + case SyntaxKind.AccessorList: case SyntaxKind.VariableDeclarator: - // allowed: private fields in classes - ClassifyFieldInsert((VariableDeclaratorSyntax)node); - return; - case SyntaxKind.VariableDeclaration: - // allowed: private fields in classes - ClassifyFieldInsert((VariableDeclarationSyntax)node); + // Adding these members is not allowed or there are limitations on them that needs to be checked. + // However, any of these members can be moved to a different file or partial type declaration, so + // we need to defer reporting rude edits till semantic analysis. return; case SyntaxKind.Parameter when !_classifyStatementSyntax: @@ -2171,100 +2135,6 @@ private void ClassifyInsert(SyntaxNode node) } } - private bool ClassifyModifiedMemberInsert(MemberDeclarationSyntax declaration, SyntaxTokenList modifiers) - { - if (modifiers.Any(SyntaxKind.ExternKeyword)) - { - ReportError(RudeEditKind.InsertExtern); - return false; - } - - var isExplicitlyVirtual = modifiers.Any(SyntaxKind.VirtualKeyword) || modifiers.Any(SyntaxKind.AbstractKeyword) || modifiers.Any(SyntaxKind.OverrideKeyword); - - var isInterfaceVirtual = - declaration.Parent.IsKind(SyntaxKind.InterfaceDeclaration) && - !declaration.IsKind(SyntaxKind.EventFieldDeclaration) && - !declaration.IsKind(SyntaxKind.FieldDeclaration) && - !modifiers.Any(SyntaxKind.SealedKeyword) && - !modifiers.Any(SyntaxKind.StaticKeyword); - - if (isExplicitlyVirtual || isInterfaceVirtual) - { - ReportError(RudeEditKind.InsertVirtual); - return false; - } - - // TODO: Adding a non-virtual member to an interface also fails at runtime - // https://github.com/dotnet/roslyn/issues/37128 - if (declaration.Parent.IsKind(SyntaxKind.InterfaceDeclaration)) - { - ReportError(RudeEditKind.InsertIntoInterface); - return false; - } - - return true; - } - - private void ClassifyTypeWithPossibleExternMembersInsert(TypeDeclarationSyntax type) - { - // extern members are not allowed, even in a new type - foreach (var member in type.Members) - { - var modifiers = default(SyntaxTokenList); - - switch (member.Kind()) - { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.IndexerDeclaration: - case SyntaxKind.EventDeclaration: - modifiers = ((BasePropertyDeclarationSyntax)member).Modifiers; - break; - - case SyntaxKind.ConversionOperatorDeclaration: - case SyntaxKind.OperatorDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.ConstructorDeclaration: - modifiers = ((BaseMethodDeclarationSyntax)member).Modifiers; - break; - } - - if (modifiers.Any(SyntaxKind.ExternKeyword)) - { - ReportError(RudeEditKind.InsertExtern, member, member); - } - } - } - - private void ClassifyMethodInsert(MethodDeclarationSyntax method) - { - ClassifyModifiedMemberInsert(method, method.Modifiers); - - if (method.Arity > 0) - { - ReportError(RudeEditKind.InsertGenericMethod); - } - - if (method.ExplicitInterfaceSpecifier != null) - { - ReportError(RudeEditKind.InsertMethodWithExplicitInterfaceSpecifier); - } - } - - private void ClassifyAccessorInsert(AccessorDeclarationSyntax accessor) - { - var baseProperty = (BasePropertyDeclarationSyntax)accessor.Parent!.Parent!; - ClassifyModifiedMemberInsert(baseProperty, baseProperty.Modifiers); - } - - private void ClassifyFieldInsert(BaseFieldDeclarationSyntax field) - => ClassifyModifiedMemberInsert(field, field.Modifiers); - - private void ClassifyFieldInsert(VariableDeclaratorSyntax fieldVariable) - => ClassifyFieldInsert((VariableDeclarationSyntax)fieldVariable.Parent!); - - private void ClassifyFieldInsert(VariableDeclarationSyntax fieldVariable) - => ClassifyFieldInsert((BaseFieldDeclarationSyntax)fieldVariable.Parent!); - #endregion #region Delete @@ -2281,15 +2151,18 @@ private void ClassifyDelete(SyntaxNode oldNode) case SyntaxKind.ExternAliasDirective: case SyntaxKind.UsingDirective: case SyntaxKind.NamespaceDeclaration: + // To allow removal of declarations we would need to update method bodies that + // were previously binding to them but now are binding to another symbol that was previously hidden. + ReportError(RudeEditKind.Delete); + return; + + case SyntaxKind.DestructorDeclaration: + case SyntaxKind.ConversionOperatorDeclaration: + case SyntaxKind.OperatorDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.DelegateDeclaration: case SyntaxKind.MethodDeclaration: - case SyntaxKind.ConversionOperatorDeclaration: - case SyntaxKind.OperatorDeclaration: - case SyntaxKind.DestructorDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.IndexerDeclaration: case SyntaxKind.EventDeclaration: @@ -2297,46 +2170,21 @@ private void ClassifyDelete(SyntaxNode oldNode) case SyntaxKind.EventFieldDeclaration: case SyntaxKind.VariableDeclarator: case SyntaxKind.VariableDeclaration: - // To allow removal of declarations we would need to update method bodies that - // were previously binding to them but now are binding to another symbol that was previously hidden. - ReportError(RudeEditKind.Delete); - return; - + case SyntaxKind.EnumDeclaration: + case SyntaxKind.DelegateDeclaration: case SyntaxKind.ConstructorDeclaration: - // Allow deletion of a parameterless constructor. - // Semantic analysis reports an error if the parameterless ctor isn't replaced by a default ctor. - if (!SyntaxUtilities.IsParameterlessConstructor(oldNode)) - { - ReportError(RudeEditKind.Delete); - } - + // We do not report member delete here since the member might be moving to a different part of a partial type declaration. + // If that is not the case the semantic analysis reports the rude edit. return; case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: - // An accessor can be removed. Accessors are not hiding other symbols. - // If the new compilation still uses the removed accessor a semantic error will be reported. - // For simplicity though we disallow deletion of accessors for now. - // The compiler would need to remember that the accessor has been deleted, - // so that its addition back is interpreted as an update. - // Additional issues might involve changing accessibility of the accessor. - ReportError(RudeEditKind.Delete); - return; - + case SyntaxKind.EnumMemberDeclaration: case SyntaxKind.AccessorList: - Debug.Assert( - oldNode.Parent.IsKind(SyntaxKind.PropertyDeclaration) || - oldNode.Parent.IsKind(SyntaxKind.IndexerDeclaration)); - - var accessorList = (AccessorListSyntax)oldNode; - var setter = accessorList.Accessors.FirstOrDefault(a => a.IsKind(SyntaxKind.SetAccessorDeclaration)); - if (setter != null) - { - ReportError(RudeEditKind.Delete, accessorList.Parent, setter); - } - + // We do not report error here since it will be reported in semantic analysis. return; case SyntaxKind.AttributeList: @@ -2347,12 +2195,6 @@ private void ClassifyDelete(SyntaxNode oldNode) ReportError(RudeEditKind.Delete); return; - case SyntaxKind.EnumMemberDeclaration: - // We could allow removing enum member if it didn't affect the values of other enum members. - // If the updated compilation binds without errors it means that the enum value wasn't used. - ReportError(RudeEditKind.Delete); - return; - case SyntaxKind.TypeParameter: case SyntaxKind.TypeParameterList: case SyntaxKind.TypeParameterConstraintClause: @@ -2465,6 +2307,7 @@ private void ClassifyUpdate(SyntaxNode oldNode, SyntaxNode newNode) case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: ClassifyUpdate((AccessorDeclarationSyntax)oldNode, (AccessorDeclarationSyntax)newNode); @@ -2522,7 +2365,8 @@ private void ClassifyUpdate(TypeDeclarationSyntax oldNode, TypeDeclarationSyntax return; } - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) + // Allow partial keyword to be added or removed. + if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.PartialKeyword)) { ReportError(RudeEditKind.ModifiersUpdate); return; @@ -2534,8 +2378,10 @@ private void ClassifyUpdate(TypeDeclarationSyntax oldNode, TypeDeclarationSyntax return; } - Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.BaseList, newNode.BaseList)); - ReportError(RudeEditKind.BaseTypeOrInterfaceUpdate); + if (!SyntaxFactory.AreEquivalent(oldNode.BaseList, newNode.BaseList)) + { + ReportError(RudeEditKind.BaseTypeOrInterfaceUpdate); + } } private void ClassifyUpdate(EnumDeclarationSyntax oldNode, EnumDeclarationSyntax newNode) @@ -2614,7 +2460,7 @@ private void ClassifyUpdate(VariableDeclaratorSyntax oldNode, VariableDeclarator // If the argument lists are mismatched the field must have mismatched "fixed" modifier, // which is reported by the field declaration. - if ((oldNode.ArgumentList == null) == (newNode.ArgumentList == null)) + if (oldNode.ArgumentList is null == newNode.ArgumentList is null) { if (!SyntaxFactory.AreEquivalent(oldNode.ArgumentList, newNode.ArgumentList)) { @@ -2649,7 +2495,8 @@ private void ClassifyUpdate(MethodDeclarationSyntax oldNode, MethodDeclarationSy return; } - if (!ClassifyMethodModifierUpdate(oldNode.Modifiers, newNode.Modifiers)) + // Ignore async keyword when matching modifiers. Async checks are done in ComputeBodyMatch. + if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.AsyncKeyword)) { ReportError(RudeEditKind.ModifiersUpdate); return; @@ -2674,27 +2521,6 @@ private void ClassifyUpdate(MethodDeclarationSyntax oldNode, MethodDeclarationSy containingType: (TypeDeclarationSyntax?)newNode.Parent); } - private static bool ClassifyMethodModifierUpdate(SyntaxTokenList oldModifiers, SyntaxTokenList newModifiers) - { - // Ignore async keyword when matching modifiers. - // async checks are done in ComputeBodyMatch. - - var oldAsyncIndex = oldModifiers.IndexOf(SyntaxKind.AsyncKeyword); - var newAsyncIndex = newModifiers.IndexOf(SyntaxKind.AsyncKeyword); - - if (oldAsyncIndex >= 0) - { - oldModifiers = oldModifiers.RemoveAt(oldAsyncIndex); - } - - if (newAsyncIndex >= 0) - { - newModifiers = newModifiers.RemoveAt(newAsyncIndex); - } - - return SyntaxFactory.AreEquivalent(oldModifiers, newModifiers); - } - private void ClassifyUpdate(ConversionOperatorDeclarationSyntax oldNode, ConversionOperatorDeclarationSyntax newNode) { if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) @@ -2960,6 +2786,24 @@ private void ClassifyUpdate(AttributeListSyntax oldNode, AttributeListSyntax new // changes in attribute separators are not interesting: } + private static bool AreModifiersEquivalent(SyntaxTokenList oldModifiers, SyntaxTokenList newModifiers, SyntaxKind ignore) + { + var oldIgnoredModifierIndex = oldModifiers.IndexOf(ignore); + var newIgnoredModifierIndex = newModifiers.IndexOf(ignore); + + if (oldIgnoredModifierIndex >= 0) + { + oldModifiers = oldModifiers.RemoveAt(oldIgnoredModifierIndex); + } + + if (newIgnoredModifierIndex >= 0) + { + newModifiers = newModifiers.RemoveAt(newIgnoredModifierIndex); + } + + return SyntaxFactory.AreEquivalent(oldModifiers, newModifiers); + } + private void ClassifyMethodBodyRudeUpdate( SyntaxNode? oldBody, SyntaxNode? newBody, @@ -3062,9 +2906,94 @@ internal override void ReportMemberUpdateRudeEdits(ArrayBuilder diagnostics, ISymbol newSymbol) + internal override void ReportInsertedMemberSymbolRudeEdits(ArrayBuilder diagnostics, ISymbol newSymbol, SyntaxNode newNode, bool insertingIntoExistingContainingType) { - // We rejected all exported methods during syntax analysis, so no additional work is needed here. + var rudeEditKind = newSymbol switch + { + // Inserting extern member into a new or existing type is not allowed. + { IsExtern: true } + => RudeEditKind.InsertExtern, + + // All rude edits below only apply when inserting into an existing type (not when the type itself is inserted): + _ when !insertingIntoExistingContainingType => RudeEditKind.None, + + // Inserting a member into an existing generic type is not allowed. + { ContainingType: { Arity: > 0 } } and not INamedTypeSymbol + => RudeEditKind.InsertIntoGenericType, + + // Inserting virtual or interface member into an existing type is not allowed. + { IsVirtual: true } or { IsOverride: true } or { IsAbstract: true } and not INamedTypeSymbol + => RudeEditKind.InsertVirtual, + + // Inserting generic method into an existing type is not allowed. + IMethodSymbol { Arity: > 0 } + => RudeEditKind.InsertGenericMethod, + + // Inserting destructor to an existing type is not allowed. + IMethodSymbol { MethodKind: MethodKind.Destructor } + => RudeEditKind.Insert, + + // Inserting operator to an existing type is not allowed. + IMethodSymbol { MethodKind: MethodKind.Conversion or MethodKind.UserDefinedOperator } + => RudeEditKind.InsertOperator, + + // Inserting a method that explictly implements an interface method into an existing type is not allowed. + IMethodSymbol { ExplicitInterfaceImplementations: { IsEmpty: false } } + => RudeEditKind.InsertMethodWithExplicitInterfaceSpecifier, + + // TODO: Inserting non-virtual member to an interface (https://github.com/dotnet/roslyn/issues/37128) + { ContainingType: { TypeKind: TypeKind.Interface } } and not INamedTypeSymbol + => RudeEditKind.InsertIntoInterface, + + _ => RudeEditKind.None + }; + + if (rudeEditKind != RudeEditKind.None) + { + diagnostics.Add(new RudeEditDiagnostic( + rudeEditKind, + GetDiagnosticSpan(newNode, EditKind.Insert), + newNode, + arguments: new[] { GetDisplayName(newNode, EditKind.Insert) })); + } + } + + internal override void ReportTypeDeclarationInsertDeleteRudeEdits(ArrayBuilder diagnostics, INamedTypeSymbol oldType, INamedTypeSymbol newType, SyntaxNode newDeclaration, CancellationToken cancellationToken) + { + using var _1 = ArrayBuilder.GetInstance(out var oldNodes); + using var _2 = ArrayBuilder.GetInstance(out var newNodes); + + // Consider: better error messages + Report((b, t) => AddNodes(b, t.AttributeLists), RudeEditKind.Update); + Report((b, t) => AddNodes(b, t.TypeParameterList?.Parameters), RudeEditKind.Update); + Report((b, t) => AddNodes(b, t.ConstraintClauses), RudeEditKind.Update); + Report((b, t) => AddNodes(b, t.BaseList?.Types), RudeEditKind.BaseTypeOrInterfaceUpdate); + + void Report(Action, TypeDeclarationSyntax> addNodes, RudeEditKind rudeEditKind) + { + foreach (var syntaxRef in oldType.DeclaringSyntaxReferences) + { + addNodes(oldNodes, (TypeDeclarationSyntax)syntaxRef.GetSyntax(cancellationToken)); + } + + foreach (var syntaxRef in newType.DeclaringSyntaxReferences) + { + addNodes(newNodes, (TypeDeclarationSyntax)syntaxRef.GetSyntax(cancellationToken)); + } + + if (oldNodes.Count != newNodes.Count || + oldNodes.Zip(newNodes, (oldNode, newNode) => SyntaxFactory.AreEquivalent(oldNode, newNode)).Any(isEquivalent => !isEquivalent)) + { + diagnostics.Add(new RudeEditDiagnostic( + rudeEditKind, + GetDiagnosticSpan(newDeclaration, EditKind.Update), + newDeclaration, + arguments: new[] { GetDisplayName(newDeclaration, EditKind.Update) })); + } + + oldNodes.Clear(); + newNodes.Clear(); + } } #endregion diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs index 4a28976e95174..515e76f771821 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs @@ -485,6 +485,7 @@ internal Label Classify(SyntaxKind kind, SyntaxNode? node, out bool isLeaf) case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: isLeaf = true; @@ -815,6 +816,7 @@ public override bool ValuesEqual(SyntaxNode left, SyntaxNode right) case SyntaxKind.DestructorDeclaration: case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: // When comparing method bodies we need to NOT ignore VariableDeclaration and VariableDeclarator children, @@ -1545,6 +1547,7 @@ private static double CombineOptional( case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: return null; case SyntaxKind.TypeParameterConstraintClause: diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs index 1b8b26a678bd4..2fccc1ff4fb88 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs @@ -38,6 +38,7 @@ static SyntaxNode BlockOrExpression(BlockSyntax blockBodyOpt, ArrowExpressionCla break; case SyntaxKind.SetAccessorDeclaration: + case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: case SyntaxKind.GetAccessorDeclaration: diff --git a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementExplicitlyCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementExplicitlyCodeRefactoringProvider.cs index b61bbfe4973a0..92aa021771479 100644 --- a/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementExplicitlyCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/ImplementInterface/CSharpImplementExplicitlyCodeRefactoringProvider.cs @@ -53,7 +53,7 @@ protected override async Task UpdateReferencesAsync( // // This can save a lot of extra time spent finding callers, especially for methods with // high fan-out (like IDisposable.Dispose()). - var findRefsOptions = FindReferencesSearchOptions.Default.WithCascade(false); + var findRefsOptions = FindReferencesSearchOptions.Default.With(cascade: false); var references = await SymbolFinder.FindReferencesAsync( implMember, solution, findRefsOptions, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs index 3d8f86805f291..0362b897f2115 100644 --- a/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeMethodAsynchronous/CSharpMakeMethodAsynchronousCodeFixProvider.cs @@ -124,7 +124,7 @@ private static TypeSyntax FixMethodReturnType( } } - return newReturnType.WithTriviaFrom(returnTypeSyntax); + return newReturnType.WithTriviaFrom(returnTypeSyntax).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation); static TypeSyntax MakeGenericType(string type, ITypeSymbol typeArgumentFrom) { diff --git a/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj b/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj index 7ff072497f0c2..c3e5bdb1cfd9e 100644 --- a/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj +++ b/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj @@ -41,7 +41,6 @@ - diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf index 21ee4e5363bbd..a084086132dc6 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf @@ -222,6 +222,11 @@ asynchronní deklarace using {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <lambda výraz> @@ -587,11 +592,6 @@ příkaz global {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - using namespace - - using directive direktiva using diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf index 6b21c135fdceb..ac01428abc6f3 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf @@ -222,6 +222,11 @@ asynchrone using-Deklaration {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <Lambdaausdruck> @@ -587,11 +592,6 @@ global – Anweisung {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - Using-Namespace - - using directive Using-Direktive diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf index bce2f1c314cb3..633a55c592b01 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf @@ -222,6 +222,11 @@ declaración using asincrónica {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <expresión lambda> @@ -587,11 +592,6 @@ declaración global {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - uso de espacio de nombres - - using directive uso de directiva diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf index 6e0d84b3d51cb..e151efe5d88c0 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf @@ -222,6 +222,11 @@ déclaration using asynchrone {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <expression lambda> @@ -587,11 +592,6 @@ instruction global {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - espace de noms using - - using directive directive using diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf index 4bde6e4bac3ae..09d2196abac8e 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf @@ -222,6 +222,11 @@ dichiarazione using asincrona {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <espressione lambda> @@ -587,11 +592,6 @@ istruzione global {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - spazio dei nomi using - - using directive direttiva using diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf index 3757eabcaaeb5..358fe3893a6a2 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf @@ -222,6 +222,11 @@ 非同期の using 宣言 {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <ラムダ式> @@ -587,11 +592,6 @@ global ステートメント {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - using namespace - - using directive using ディレクティブ diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf index 33552037888d1..b996a9e802faf 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf @@ -222,6 +222,11 @@ 비동기 using 선언 {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <람다 식> @@ -587,11 +592,6 @@ global 문 {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - using 네임스페이스 - - using directive using 지시문 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf index ab28e8a0cd3c0..cc430b72cb1f1 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf @@ -222,6 +222,11 @@ asynchroniczna deklaracja using {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <wyrażenie lambda> @@ -587,11 +592,6 @@ instrukcja global {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - przestrzeń nazw using - - using directive dyrektywa using diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf index dde6beb05a878..a0809829b0b83 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf @@ -222,6 +222,11 @@ declaração using assíncrona {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <expressão lambda> @@ -587,11 +592,6 @@ instrução global {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - usando o namespace - - using directive usando a diretiva diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf index 7db755f950b9e..8d2a6cdc26bac 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf @@ -222,6 +222,11 @@ асинхронное объявление using {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <лямбда-выражение> @@ -587,11 +592,6 @@ инструкция global {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - пространство имен using - - using directive директива using diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf index 4cfe2851c6a60..71d15b62707de 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf @@ -222,6 +222,11 @@ zaman uyumsuz using bildirimi {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> < lambda ifadesi > @@ -587,11 +592,6 @@ global deyimi {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - using namespace - - using directive using yönergesi diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf index 59a8aab8dd156..c09ea9a7428fe 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf @@ -222,6 +222,11 @@ 异步 using 声明 {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <lambda 表达式> @@ -587,11 +592,6 @@ global 语句 {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - using 命名空间 - - using directive using 指令 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf index 849cf4e721a03..ba4f7d7d06721 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf @@ -222,6 +222,11 @@ 非同步 using 宣告 {Locked="using"} "using" is a C# keyword and should not be localized. + + extern alias + extern alias + + <lambda expression> <Lambda 運算式> @@ -587,11 +592,6 @@ global 陳述式 {Locked="global"} "global" is a C# keyword and should not be localized. - - using namespace - using 命名空間 - - using directive using 指示詞 diff --git a/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs b/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs index f88628805530b..f25391ebe9b17 100644 --- a/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs +++ b/src/Features/Core/Portable/CodeLens/CodeLensReferencesService.cs @@ -26,6 +26,12 @@ internal sealed class CodeLensReferencesService : ICodeLensReferencesService typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, memberOptions: SymbolDisplayMemberOptions.IncludeContainingType); + // Set ourselves as an implicit invocation of FindReferences. This will cause the finding operation to operate + // in serial, not parallel. We're running ephemerally in the BG and do not want to saturate the system with + // work that then slows the user down. + private static readonly FindReferencesSearchOptions s_nonParallelSearch = + FindReferencesSearchOptions.Default.With(@explicit: false); + private static async Task FindAsync(Solution solution, DocumentId documentId, SyntaxNode syntaxNode, Func> onResults, Func> onCapped, int searchCap, CancellationToken cancellationToken) where T : struct @@ -49,8 +55,8 @@ internal sealed class CodeLensReferencesService : ICodeLensReferencesService using var progress = new CodeLensFindReferencesProgress(symbol, syntaxNode, searchCap, cancellationToken); try { - await SymbolFinder.FindReferencesAsync(symbol, solution, progress, null, - progress.CancellationToken).ConfigureAwait(false); + await SymbolFinder.FindReferencesAsync( + symbol, solution, progress, documents: null, s_nonParallelSearch, progress.CancellationToken).ConfigureAwait(false); return await onResults(progress).ConfigureAwait(false); } diff --git a/src/Features/Core/Portable/Completion/CompletionOptions.cs b/src/Features/Core/Portable/Completion/CompletionOptions.cs index 74db59f805726..776a9010bd762 100644 --- a/src/Features/Core/Portable/Completion/CompletionOptions.cs +++ b/src/Features/Core/Portable/Completion/CompletionOptions.cs @@ -63,6 +63,14 @@ internal static class CompletionOptions new(nameof(CompletionOptions), nameof(TriggerInArgumentLists), defaultValue: true, storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.TriggerInArgumentLists")); + // Test-only options + + // This option is associated with the Roslyn.LSP.Completion flag and should be removed once the flag is removed. + // It is intended for testing purposes only. + public static readonly PerLanguageOption2 ForceRoslynLSPCompletionExperiment = + new(nameof(CompletionOptions), nameof(ForceRoslynLSPCompletionExperiment), defaultValue: false, + storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.ForceRoslynLSPCompletionExperiment")); + public static IEnumerable> GetDev15CompletionOptions() { yield return ShowCompletionItemFilters; diff --git a/src/Features/Core/Portable/Completion/CompletionServiceOptions.cs b/src/Features/Core/Portable/Completion/CompletionServiceOptions.cs index e059d2fb688c3..15df57e3e42a4 100644 --- a/src/Features/Core/Portable/Completion/CompletionServiceOptions.cs +++ b/src/Features/Core/Portable/Completion/CompletionServiceOptions.cs @@ -14,6 +14,12 @@ internal static class CompletionServiceOptions public static readonly Option2 IsExpandedCompletion = new(nameof(CompletionServiceOptions), nameof(IsExpandedCompletion), defaultValue: false); + /// + /// For testing only. Changing the default value in actual product might cause perf issues. + /// + public static readonly Option2 UsePartialSemanticForImportCompletion + = new(nameof(CompletionServiceOptions), nameof(UsePartialSemanticForImportCompletion), defaultValue: true); + /// /// Indicates if the completion should be disallowed to add imports. /// diff --git a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs index 22cce31897d64..b5f68b5edb547 100644 --- a/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs @@ -22,7 +22,7 @@ namespace Microsoft.CodeAnalysis.Completion.Providers { internal abstract class AbstractImportCompletionProvider : LSPCompletionProvider { - protected abstract Task CreateContextAsync(Document document, int position, CancellationToken cancellationToken); + protected abstract Task CreateContextAsync(Document document, int position, bool usePartialSemantic, CancellationToken cancellationToken); protected abstract ImmutableArray GetImportedNamespaces(SyntaxNode location, SemanticModel semanticModel, CancellationToken cancellationToken); protected abstract bool ShouldProvideCompletion(CompletionContext completionContext, SyntaxContext syntaxContext); protected abstract Task AddCompletionItemsAsync(CompletionContext completionContext, SyntaxContext syntaxContext, HashSet namespacesInScope, bool isExpandedCompletion, CancellationToken cancellationToken); @@ -54,7 +54,8 @@ public override async Task ProvideCompletionsAsync(CompletionContext completionC // We need to check for context before option values, so we can tell completion service that we are in a context to provide expanded items // even though import completion might be disabled. This would show the expander in completion list which user can then use to explicitly ask for unimported items. - var syntaxContext = await CreateContextAsync(document, completionContext.Position, cancellationToken).ConfigureAwait(false); + var usePartialSemantic = completionContext.Options.GetOption(CompletionServiceOptions.UsePartialSemanticForImportCompletion); + var syntaxContext = await CreateContextAsync(document, completionContext.Position, usePartialSemantic, cancellationToken).ConfigureAwait(false); if (!ShouldProvideCompletion(completionContext, syntaxContext)) { return; diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index f6c233dbeac5d..4197c8040823b 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -27,6 +27,42 @@ internal abstract class AbstractEditAndContinueAnalyzer : IEditAndContinueAnalyz { internal const int DefaultStatementPart = 0; + /// + /// Contains enough information to determine whether two symbols have the same signature. + /// + private static readonly SymbolDisplayFormat s_unqualifiedMemberDisplayFormat = + new( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly, + propertyStyle: SymbolDisplayPropertyStyle.NameOnly, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance, + memberOptions: + SymbolDisplayMemberOptions.IncludeParameters | + SymbolDisplayMemberOptions.IncludeExplicitInterface, + parameterOptions: + SymbolDisplayParameterOptions.IncludeParamsRefOut | + SymbolDisplayParameterOptions.IncludeExtensionThis | + SymbolDisplayParameterOptions.IncludeType, + miscellaneousOptions: + SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + + private static readonly SymbolDisplayFormat s_fullyQualifiedMemberDisplayFormat = + new( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, + propertyStyle: SymbolDisplayPropertyStyle.NameOnly, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance, + memberOptions: + SymbolDisplayMemberOptions.IncludeParameters | + SymbolDisplayMemberOptions.IncludeExplicitInterface | + SymbolDisplayMemberOptions.IncludeContainingType, + parameterOptions: + SymbolDisplayParameterOptions.IncludeParamsRefOut | + SymbolDisplayParameterOptions.IncludeExtensionThis | + SymbolDisplayParameterOptions.IncludeType, + miscellaneousOptions: + SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + // used by tests to validate correct handlign of unexpected exceptions private readonly Action? _testFaultInjector; @@ -57,9 +93,7 @@ protected AbstractEditAndContinueAnalyzer(Action? testFaultInjector) /// i.e. a node used as the root of statement-level match. /// /// A node representing a declaration or a top-level edit node. - /// - /// True if represents a member declaration, - /// false if it represents an edit node. + /// /// /// Returns null for nodes that don't represent declarations. /// @@ -71,30 +105,26 @@ protected AbstractEditAndContinueAnalyzer(Action? testFaultInjector) /// /// Body does not need to cover all active statements that may be associated with the member. /// E.g. Body of a C# constructor is the method body block. Active statements may be placed on the base constructor call. - /// Body if a VB field declaration with shared AsNew initializer is the New expression. Active statements might be placed on the field variables. + /// Body of a VB field declaration with shared AsNew initializer is the New expression. Active statements might be placed on the field variables. /// has to account for such cases. /// - internal abstract SyntaxNode? TryGetDeclarationBody(SyntaxNode node, bool isMember); + internal abstract SyntaxNode? TryGetDeclarationBody(SyntaxNode node); /// /// Interprets an edit as a declaration body edit. /// - /// A top-level edit. - /// All top-level edits by syntax node. + /// Old member declaration node. + /// New member declaration node. /// The old body participating in the edit. /// The new body participating in the edit. - /// - /// True if the specified edit is a declaration body edit, false otherwise. - /// - protected virtual bool TryGetDeclarationBodyEdit( - Edit edit, - IReadOnlyDictionary editMap, + protected virtual void GetUpdatedDeclarationBodies( + SyntaxNode oldDeclaration, + SyntaxNode newDeclaration, out SyntaxNode? oldBody, out SyntaxNode? newBody) { - oldBody = (edit.OldNode != null) ? TryGetDeclarationBody(edit.OldNode, isMember: false) : null; - newBody = (edit.NewNode != null) ? TryGetDeclarationBody(edit.NewNode, isMember: false) : null; - return true; + oldBody = TryGetDeclarationBody(oldDeclaration); + newBody = TryGetDeclarationBody(newDeclaration); } /// @@ -172,6 +202,7 @@ private SyntaxNode FindStatement(SyntaxNode declarationBody, TextSpan span, out protected abstract Match ComputeTopLevelMatch(SyntaxNode oldCompilationUnit, SyntaxNode newCompilationUnit); protected abstract Match ComputeBodyMatch(SyntaxNode oldBody, SyntaxNode newBody, IEnumerable>? knownMatches); + protected abstract Match ComputeTopLevelDeclarationMatch(SyntaxNode oldDeclaration, SyntaxNode newDeclaration); protected abstract IEnumerable GetSyntaxSequenceEdits(ImmutableArray oldNodes, ImmutableArray newNodes); /// @@ -228,7 +259,26 @@ protected virtual bool StateMachineSuspensionPointKindEquals(SyntaxNode suspensi /// protected abstract bool AreEquivalentActiveStatements(SyntaxNode oldStatement, SyntaxNode newStatement, int statementPart); - protected abstract ISymbol? GetSymbolForEdit(SemanticModel model, SyntaxNode node, EditKind editKind, IReadOnlyDictionary editMap, CancellationToken cancellationToken); + /// + /// Returns the symbol associated with an edit of the specified . + /// + /// Semantic model + /// Edit node + /// Edit kind + /// Edit map + /// + /// True if the node edit is associated with multiple symbols. + /// The returned symbol is one of these symbols. + /// All symbols must have the same containing symbol. + /// + /// Cancellation token + protected abstract ISymbol? GetSymbolForEdit( + SemanticModel model, + SyntaxNode node, + EditKind editKind, + IReadOnlyDictionary editMap, + out bool isAmbiguous, + CancellationToken cancellationToken); /// /// Analyzes data flow in the member body represented by the specified node and returns all captured variables and parameters (including "this"). @@ -312,8 +362,9 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind internal abstract void ReportEnclosingExceptionHandlingRudeEdits(ArrayBuilder diagnostics, IEnumerable> exceptionHandlingEdits, SyntaxNode oldStatement, TextSpan newStatementSpan); internal abstract void ReportOtherRudeEditsAroundActiveStatement(ArrayBuilder diagnostics, Match match, SyntaxNode oldStatement, SyntaxNode newStatement, bool isNonLeaf); internal abstract void ReportMemberUpdateRudeEdits(ArrayBuilder diagnostics, SyntaxNode newMember, TextSpan? span); - internal abstract void ReportInsertedMemberSymbolRudeEdits(ArrayBuilder diagnostics, ISymbol newSymbol); + internal abstract void ReportInsertedMemberSymbolRudeEdits(ArrayBuilder diagnostics, ISymbol newSymbol, SyntaxNode newNode, bool insertingIntoExistingContainingType); internal abstract void ReportStateMachineSuspensionPointRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode); + internal abstract void ReportTypeDeclarationInsertDeleteRudeEdits(ArrayBuilder diagnostics, INamedTypeSymbol oldSymbol, INamedTypeSymbol newSymbol, SyntaxNode newDeclaration, CancellationToken cancellationToken); internal abstract bool IsLambda(SyntaxNode node); internal abstract bool IsInterfaceDeclaration(SyntaxNode node); @@ -349,8 +400,19 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind internal abstract bool TryGetLambdaBodies(SyntaxNode node, [NotNullWhen(true)] out SyntaxNode? body1, out SyntaxNode? body2); internal abstract bool IsStateMachineMethod(SyntaxNode declaration); + + /// + /// Returns the type declaration that contains a specified . + /// This can be class, struct, interface, record or enum declaration. + /// internal abstract SyntaxNode? TryGetContainingTypeDeclaration(SyntaxNode node); + /// + /// Returns a property, indexer or event declaration whose accessor is the specified , + /// or null if is not an accessor. + /// + internal abstract SyntaxNode? TryGetAssociatedMemberDeclaration(SyntaxNode node); + internal abstract bool HasBackingField(SyntaxNode propertyDeclaration); /// @@ -365,6 +427,7 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind internal abstract bool IsConstructorWithMemberInitializers(SyntaxNode declaration); internal abstract bool IsPartial(INamedTypeSymbol type); + internal abstract SyntaxNode EmptyCompilationUnit { get; } private static readonly SourceText s_emptySource = SourceText.From(""); @@ -372,17 +435,20 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind #region Document Analysis public async Task AnalyzeDocumentAsync( - Project baseProject, - ImmutableArray baseActiveStatements, - Document document, + Project oldProject, + ImmutableArray oldActiveStatements, + Document newDocument, ImmutableArray newActiveStatementSpans, CancellationToken cancellationToken) { - DocumentAnalysisResults.Log.Write("Analyzing document {0}", document.Name); + DocumentAnalysisResults.Log.Write("Analyzing document {0}", newDocument.Name); Debug.Assert(!newActiveStatementSpans.IsDefault); - Debug.Assert(document.SupportsSyntaxTree); - Debug.Assert(document.SupportsSemanticModel); + Debug.Assert(newDocument.SupportsSyntaxTree); + Debug.Assert(newDocument.SupportsSemanticModel); + + // assume changes until we determine there are none so that EnC is blocked on unexpected exception: + var hasChanges = true; try { @@ -392,7 +458,7 @@ public async Task AnalyzeDocumentAsync( SyntaxNode oldRoot; SourceText oldText; - var oldDocument = baseProject.GetDocument(document.Id); + var oldDocument = oldProject.GetDocument(newDocument.Id); if (oldDocument != null) { oldTree = await oldDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); @@ -408,7 +474,7 @@ public async Task AnalyzeDocumentAsync( oldText = s_emptySource; } - var newTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var newTree = await newDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(newTree); // Changes in parse options might change the meaning of the code even if nothing else changed. @@ -416,8 +482,8 @@ public async Task AnalyzeDocumentAsync( Debug.Assert(oldTree == null || oldTree.Options.Equals(newTree.Options)); var newRoot = await newTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var hasChanges = !oldText.ContentEquals(newText); + var newText = await newDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + hasChanges = !oldText.ContentEquals(newText); _testFaultInjector?.Invoke(newRoot); cancellationToken.ThrowIfCancellationRequested(); @@ -429,15 +495,15 @@ public async Task AnalyzeDocumentAsync( { // Bail, since we can't do syntax diffing on broken trees (it would not produce useful results anyways). // If we needed to do so for some reason, we'd need to harden the syntax tree comparers. - DocumentAnalysisResults.Log.Write("{0}: syntax errors", document.Name); - return DocumentAnalysisResults.SyntaxErrors(document.Id, hasChanges); + DocumentAnalysisResults.Log.Write("{0}: syntax errors", newDocument.Name); + return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, ImmutableArray.Empty, hasChanges); } - var newActiveStatements = ImmutableArray.CreateBuilder(baseActiveStatements.Length); - newActiveStatements.Count = baseActiveStatements.Length; + var newActiveStatements = ImmutableArray.CreateBuilder(oldActiveStatements.Length); + newActiveStatements.Count = oldActiveStatements.Length; - var newExceptionRegions = ImmutableArray.CreateBuilder>(baseActiveStatements.Length); - newExceptionRegions.Count = baseActiveStatements.Length; + var newExceptionRegions = ImmutableArray.CreateBuilder>(oldActiveStatements.Length); + newExceptionRegions.Count = oldActiveStatements.Length; if (!hasChanges) { @@ -447,24 +513,24 @@ public async Task AnalyzeDocumentAsync( // b) we need to ignore errors in unchanged documents AnalyzeUnchangedDocument( - baseActiveStatements, + oldActiveStatements, newText, newRoot, newActiveStatements, newExceptionRegions); - DocumentAnalysisResults.Log.Write("{0}: unchanged", document.Name); - return DocumentAnalysisResults.Unchanged(document.Id, newActiveStatements.MoveToImmutable(), newExceptionRegions.MoveToImmutable()); + DocumentAnalysisResults.Log.Write("{0}: unchanged", newDocument.Name); + return DocumentAnalysisResults.Unchanged(newDocument.Id, newActiveStatements.MoveToImmutable(), newExceptionRegions.MoveToImmutable()); } // Disallow modification of a file with experimental features enabled. // These features may not be handled well by the analysis below. if (ExperimentalFeaturesEnabled(newTree)) { - DocumentAnalysisResults.Log.Write("{0}: experimental features enabled", document.Name); + DocumentAnalysisResults.Log.Write("{0}: experimental features enabled", newDocument.Name); - return DocumentAnalysisResults.Errors(document.Id, activeStatementsOpt: default, ImmutableArray.Create( - new RudeEditDiagnostic(RudeEditKind.ExperimentalFeaturesEnabled, default))); + return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, ImmutableArray.Create( + new RudeEditDiagnostic(RudeEditKind.ExperimentalFeaturesEnabled, default)), hasChanges); } // We do calculate diffs even if there are semantic errors for the following reasons: @@ -473,7 +539,6 @@ public async Task AnalyzeDocumentAsync( // 2) If there are syntactic rude edits we'll report them faster without waiting for semantic analysis. // The user may fix them before they address all the semantic errors. - using var _1 = ArrayBuilder.GetInstance(out var updatedMembers); using var _2 = ArrayBuilder.GetInstance(out var diagnostics); cancellationToken.ThrowIfCancellationRequested(); @@ -481,35 +546,14 @@ public async Task AnalyzeDocumentAsync( var topMatch = ComputeTopLevelMatch(oldRoot, newRoot); var syntacticEdits = topMatch.GetTreeEdits(); var editMap = BuildEditMap(syntacticEdits); - - AnalyzeMemberBodiesSyntax( - syntacticEdits, - editMap, - oldText, - newText, - baseActiveStatements, - newActiveStatementSpans, - newActiveStatements, - newExceptionRegions, - updatedMembers, - diagnostics); + var hasRudeEdits = false; ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap); - if (diagnostics.Count > 0) - { - DocumentAnalysisResults.Log.Write("{0} syntactic rude edits, first: '{1}'", diagnostics.Count, document.FilePath); - return DocumentAnalysisResults.Errors(document.Id, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable()); - } - - // Disallow addition of a new file. - // During EnC, a new file cannot be added to the current solution, but some IDE features (i.e., CodeFix) try to do so. - // In most cases, syntactic rude edits detect them with specific reasons but some reach up to here and we bail them out with a general message. - if (oldDocument == null) + if (diagnostics.Count > 0 && !hasRudeEdits) { - DocumentAnalysisResults.Log.Write("A new file added: {0}", document.Name); - return DocumentAnalysisResults.Errors(document.Id, newActiveStatements.MoveToImmutable(), ImmutableArray.Create( - new RudeEditDiagnostic(RudeEditKind.InsertFile, default))); + DocumentAnalysisResults.Log.Write("{0} syntactic rude edits, first: '{1}'", diagnostics.Count, newDocument.FilePath); + hasRudeEdits = true; } cancellationToken.ThrowIfCancellationRequested(); @@ -517,66 +561,64 @@ public async Task AnalyzeDocumentAsync( using var _3 = ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)>.GetInstance(out var triviaEdits); using var _4 = ArrayBuilder.GetInstance(out var lineEdits); - AnalyzeTrivia( - oldText, - newText, - topMatch, - editMap, - triviaEdits, - lineEdits, - diagnostics, - cancellationToken); - - cancellationToken.ThrowIfCancellationRequested(); - - if (diagnostics.Count > 0) - { - DocumentAnalysisResults.Log.Write("{0} trivia rude edits, first: {1}@{2}", diagnostics.Count, document.FilePath, diagnostics.First().Span.Start); - return DocumentAnalysisResults.Errors(document.Id, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable()); - } - - cancellationToken.ThrowIfCancellationRequested(); - - var semanticEdits = ImmutableArray.Empty; - if (syntacticEdits.Edits.Length > 0 || triviaEdits.Count > 0) + // Do not analyze trivia in presence of syntactic rude edits. + // The implementation depends on edit map capturing all updates and inserts, + // which might not be the case when rude edits are reported. + if (diagnostics.Count == 0) { - var newModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var oldModel = await oldDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(oldModel); - Contract.ThrowIfNull(newModel); - - using var _ = ArrayBuilder.GetInstance(out var semanticEditsBuilder); - AnalyzeSemantics( - syntacticEdits, - editMap, + AnalyzeTrivia( oldText, - baseActiveStatements, + newText, + topMatch, + editMap, triviaEdits, - updatedMembers, - oldModel, - newModel, - semanticEditsBuilder, + lineEdits, diagnostics, cancellationToken); - cancellationToken.ThrowIfCancellationRequested(); - - if (diagnostics.Count > 0) + if (diagnostics.Count > 0 && !hasRudeEdits) { - DocumentAnalysisResults.Log.Write("{0}@{1}: semantic rude edit ({2} total)", document.FilePath, diagnostics.First().Span.Start, diagnostics.Count); - return DocumentAnalysisResults.Errors(document.Id, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable()); + DocumentAnalysisResults.Log.Write("{0} trivia rude edits, first: {1}@{2}", diagnostics.Count, newDocument.FilePath, diagnostics.First().Span.Start); + hasRudeEdits = true; } - semanticEdits = semanticEditsBuilder.ToImmutable(); + cancellationToken.ThrowIfCancellationRequested(); + } + + var semanticEdits = await AnalyzeSemanticsAsync( + syntacticEdits, + editMap, + oldText, + newText, + oldActiveStatements, + newActiveStatementSpans, + triviaEdits, + oldProject, + oldDocument, + newDocument, + diagnostics, + newActiveStatements, + newExceptionRegions, + cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + AnalyzeUnchangedMemberBodies(diagnostics, syntacticEdits.Match, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions); + Debug.Assert(newActiveStatements.All(a => a != null)); + + if (diagnostics.Count > 0 && !hasRudeEdits) + { + DocumentAnalysisResults.Log.Write("{0}@{1}: rude edit ({2} total)", newDocument.FilePath, diagnostics.First().Span.Start, diagnostics.Count); + hasRudeEdits = true; } return new DocumentAnalysisResults( - document.Id, + newDocument.Id, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable(), - semanticEdits, - newExceptionRegions.MoveToImmutable(), - lineEdits.ToImmutable(), + hasRudeEdits ? default : semanticEdits, + hasRudeEdits ? default : newExceptionRegions.MoveToImmutable(), + hasRudeEdits ? default : lineEdits.ToImmutable(), hasChanges: true, hasSyntaxErrors: false); } @@ -587,10 +629,11 @@ public async Task AnalyzeDocumentAsync( // In such case we report a rude edit for the document. If the host is actually running out of memory, // it might throw another OOM here or later on. var diagnostic = (e is OutOfMemoryException) ? - new RudeEditDiagnostic(RudeEditKind.SourceFileTooBig, span: default, arguments: new[] { document.FilePath }) : - new RudeEditDiagnostic(RudeEditKind.InternalError, span: default, arguments: new[] { document.FilePath, e.ToString() }); + new RudeEditDiagnostic(RudeEditKind.SourceFileTooBig, span: default, arguments: new[] { newDocument.FilePath }) : + new RudeEditDiagnostic(RudeEditKind.InternalError, span: default, arguments: new[] { newDocument.FilePath, e.ToString() }); - return DocumentAnalysisResults.Errors(document.Id, activeStatementsOpt: default, ImmutableArray.Create(diagnostic)); + // Report as "syntax error" - we can't analyze the document + return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, ImmutableArray.Create(diagnostic), hasChanges); } } @@ -602,6 +645,26 @@ private void ReportTopLevelSyntacticRudeEdits(ArrayBuilder d } } + /// + /// Reports rude edits for a symbol that's been deleted in one location and inserted in another and the edit was not classified as + /// or . + /// The scenarios include moving a type declaration from one file to another and moving a member of a partial type from one partial declaration to another. + /// + internal void ReportDeclarationInsertDeleteRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode) + { + // Consider replacing following syntax analysis with semantic analysis of the corresponding symbols, + // or a combination of semantic and syntax analysis (e.g. primarily analyze symbols but fall back + // to syntax analysis for comparisons of attribute values, optional parameter values, etc.). + // Such approach would likely be simpler and allow us to handle more cases. + + var match = ComputeTopLevelDeclarationMatch(oldNode, newNode); + var syntacticEdits = match.GetTreeEdits(); + var editMap = BuildEditMap(syntacticEdits); + ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap); + + ReportMemberUpdateRudeEdits(diagnostics, newNode, GetDiagnosticSpan(newNode, EditKind.Update)); + } + internal static Dictionary BuildEditMap(EditScript editScript) { var map = new Dictionary(editScript.Edits.Length); @@ -626,35 +689,7 @@ internal static Dictionary BuildEditMap(EditScript script, - IReadOnlyDictionary editMap, - SourceText oldText, - SourceText newText, - ImmutableArray oldActiveStatements, - ImmutableArray newActiveStatementSpans, - [Out] ImmutableArray.Builder newActiveStatements, - [Out] ImmutableArray>.Builder newExceptionRegions, - [Out] ArrayBuilder updatedMembers, - [Out] ArrayBuilder diagnostics) - { - Debug.Assert(!newActiveStatementSpans.IsDefault); - Debug.Assert(newActiveStatementSpans.IsEmpty || oldActiveStatements.Length == newActiveStatementSpans.Length); - Debug.Assert(oldActiveStatements.Length == newActiveStatements.Count); - Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Count); - Debug.Assert(updatedMembers.Count == 0); - - for (var i = 0; i < script.Edits.Length; i++) - { - AnalyzeChangedMemberBody(script, i, editMap, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions, updatedMembers, diagnostics); - } - - AnalyzeUnchangedMemberBodies(diagnostics, script.Match, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions); - - Debug.Assert(newActiveStatements.All(a => a != null)); - } + #region Syntax Analysis private void AnalyzeUnchangedMemberBodies( ArrayBuilder diagnostics, @@ -702,8 +737,8 @@ private void AnalyzeUnchangedMemberBodies( var hasPartner = topMatch.TryGetNewNode(oldMember, out var newMember); Contract.ThrowIfFalse(hasPartner); - var oldBody = TryGetDeclarationBody(oldMember, isMember: true); - var newBody = TryGetDeclarationBody(newMember, isMember: true); + var oldBody = TryGetDeclarationBody(oldMember); + var newBody = TryGetDeclarationBody(newMember); // Guard against invalid active statement spans (in case PDB was somehow out of sync with the source). if (oldBody == null || newBody == null) @@ -768,8 +803,8 @@ private void AnalyzeUnchangedDocument( ImmutableArray oldActiveStatements, SourceText newText, SyntaxNode newRoot, - [In, Out] ImmutableArray.Builder newActiveStatements, - [In, Out] ImmutableArray>.Builder newExceptionRegions) + [Out] ImmutableArray.Builder newActiveStatements, + [Out] ImmutableArray>.Builder newExceptionRegions) { Debug.Assert(oldActiveStatements.Length == newActiveStatements.Count); Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Count); @@ -777,7 +812,7 @@ private void AnalyzeUnchangedDocument( // Active statements in methods that were not updated // are not changed but their spans might have been. - for (var i = 0; i < newActiveStatements.Count; i++) + for (var i = 0; i < oldActiveStatements.Length; i++) { if (!TryGetTextSpan(newText.Lines, oldActiveStatements[i].Span, out var oldStatementSpan) || !TryGetEnclosingBreakpointSpan(newRoot, oldStatementSpan.Start, out var newStatementSpan)) @@ -844,115 +879,33 @@ public LambdaInfo WithMatch(Match match, SyntaxNode newLambdaBody) => new(ActiveNodeIndices, match, newLambdaBody); } - internal readonly struct UpdatedMemberInfo - { - // Index in top edit script. - public readonly int EditOrdinal; - - // node that represents the old body of the method: - public readonly SyntaxNode OldBody; - - // node that represents the new body of the method: - public readonly SyntaxNode NewBody; - - // { NewNode <-> OldNode } - public readonly BidirectionalMap Map; - - // { OldLambdaBody -> LambdaInfo } - public readonly IReadOnlyDictionary? ActiveOrMatchedLambdas; - - // the method has an active statement (the statement might be in the body itself or in a lambda) - public readonly bool HasActiveStatement; - - // The old method body has a suspension point (await/yield); - // only true if the body itself has the suspension point, not if it contains async/iterator lambda - public readonly bool OldHasStateMachineSuspensionPoint; - - // The new method body has a suspension point (await/yield); - // only true if the body itself has the suspension point, not if it contains async/iterator lambda - public readonly bool NewHasStateMachineSuspensionPoint; - - public UpdatedMemberInfo( - int editOrdinal, - SyntaxNode oldBody, - SyntaxNode newBody, - BidirectionalMap map, - IReadOnlyDictionary? activeOrMatchedLambdas, - bool hasActiveStatement, - bool oldHasStateMachineSuspensionPoint, - bool newHasStateMachineSuspensionPoint) - { - Debug.Assert(editOrdinal >= 0); - Debug.Assert(!map.IsDefaultOrEmpty); - - EditOrdinal = editOrdinal; - OldBody = oldBody; - NewBody = newBody; - Map = map; - ActiveOrMatchedLambdas = activeOrMatchedLambdas; - HasActiveStatement = hasActiveStatement; - OldHasStateMachineSuspensionPoint = oldHasStateMachineSuspensionPoint; - NewHasStateMachineSuspensionPoint = newHasStateMachineSuspensionPoint; - } - } - private void AnalyzeChangedMemberBody( - EditScript topEditScript, - int editOrdinal, - IReadOnlyDictionary editMap, + SyntaxNode oldDeclaration, + SyntaxNode newDeclaration, + SyntaxNode oldBody, + SyntaxNode? newBody, SourceText oldText, SourceText newText, + SemanticModel oldModel, + SemanticModel newModel, + ISymbol oldSymbol, + ISymbol newSymbol, ImmutableArray oldActiveStatements, ImmutableArray newActiveStatementSpans, [Out] ImmutableArray.Builder newActiveStatements, [Out] ImmutableArray>.Builder newExceptionRegions, - [Out] ArrayBuilder updatedMembers, - [Out] ArrayBuilder diagnostics) + [Out] ArrayBuilder diagnostics, + out Func? syntaxMap, + CancellationToken cancellationToken) { Debug.Assert(!newActiveStatementSpans.IsDefault); Debug.Assert(newActiveStatementSpans.IsEmpty || oldActiveStatements.Length == newActiveStatementSpans.Length); - Debug.Assert(oldActiveStatements.Length == newActiveStatements.Count); - Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Count); - - var edit = topEditScript.Edits[editOrdinal]; - - // new code can't contain active statements, code that moved doesn't contain updates: - if (edit.Kind == EditKind.Insert || edit.Kind == EditKind.Reorder || edit.Kind == EditKind.Move) - { - return; - } + Debug.Assert(oldActiveStatements.IsEmpty || oldActiveStatements.Length == newActiveStatements.Count); + Debug.Assert(newActiveStatements.Count == newExceptionRegions.Count); - if (!TryGetDeclarationBodyEdit(edit, editMap, out var oldBody, out var newBody) || oldBody == null) - { - return; - } - - var hasActiveStatement = TryGetOverlappingActiveStatements(oldText, edit.OldNode.Span, oldActiveStatements, out var start, out var end); - - if (edit.Kind == EditKind.Delete) - { - // The entire member has been deleted. - - // TODO: if the member isn't a field/property we should return empty span. - // We need to adjust the tracking span design and UpdateUneditedSpans to account for such empty spans. - if (hasActiveStatement) - { - var newSpan = IsDeclarationWithInitializer(edit.OldNode) ? - GetDeletedNodeActiveSpan(topEditScript.Match.Matches, edit.OldNode) : - GetDeletedNodeDiagnosticSpan(topEditScript.Match.Matches, edit.OldNode); - - for (var i = start; i < end; i++) - { - // TODO: VB field multi-initializers break this - // Debug.Assert(newActiveStatements[i] == default(LinePositionSpan)); - - newActiveStatements[i] = oldActiveStatements[i].WithSpan(newText.Lines.GetLinePositionSpan(newSpan)); - newExceptionRegions[i] = ImmutableArray.Create(); - } - } + syntaxMap = null; - return; - } + var hasActiveStatement = TryGetOverlappingActiveStatements(oldText, oldDeclaration.Span, oldActiveStatements, out var start, out var end); if (newBody == null) { @@ -960,7 +913,7 @@ private void AnalyzeChangedMemberBody( if (hasActiveStatement) { - var newSpan = FindClosestActiveSpan(edit.NewNode, DefaultStatementPart); + var newSpan = FindClosestActiveSpan(newDeclaration, DefaultStatementPart); for (var i = start; i < end; i++) { Debug.Assert(newActiveStatements[i] == null && newSpan != default); @@ -1016,7 +969,7 @@ private void AnalyzeChangedMemberBody( { // The tracking span might have been deleted or moved outside of the member span. // It is not an error to move the statement - we just ignore it. - if (trackedSpan.Value.Length != 0 && edit.NewNode.Span.Contains(trackedSpan.Value)) + if (trackedSpan.Value.Length != 0 && newDeclaration.Span.Contains(trackedSpan.Value)) { var newStatementSyntax = FindStatement(newBody, trackedSpan.Value, out var part); Contract.ThrowIfNull(newStatementSyntax); @@ -1039,10 +992,43 @@ private void AnalyzeChangedMemberBody( var bodyMatch = ComputeBodyMatch(oldBody, newBody, activeNodes.Where(n => n.EnclosingLambdaBody == null).ToArray(), diagnostics, out var oldHasStateMachineSuspensionPoint, out var newHasStateMachineSuspensionPoint); var map = ComputeMap(bodyMatch, activeNodes, ref lazyActiveOrMatchedLambdas, diagnostics); - // Save the body match for local variable mapping. - // We'll use it to tell the compiler what local variables to preserve in an active method. - // An edited async/iterator method is considered active. - updatedMembers.Add(new UpdatedMemberInfo(editOrdinal, oldBody, newBody, map, lazyActiveOrMatchedLambdas, hasActiveStatement, oldHasStateMachineSuspensionPoint, newHasStateMachineSuspensionPoint)); + if (oldHasStateMachineSuspensionPoint) + { + ReportStateMachineRudeEdits(oldModel.Compilation, oldSymbol, newBody, diagnostics); + } + + ReportLambdaAndClosureRudeEdits( + oldModel, + oldBody, + newModel, + newBody, + newSymbol, + lazyActiveOrMatchedLambdas, + map, + diagnostics, + out var newBodyHasLambdas, + cancellationToken); + + // We need to provide syntax map to the compiler if + // 1) The new member has a active statement + // The values of local variables declared or synthesized in the method have to be preserved. + // 2) The new member generates a state machine + // In case the state machine is suspended we need to preserve variables. + // 3) The new member contains lambdas + // We need to map new lambdas in the method to the matching old ones. + // If the old method has lambdas but the new one doesn't there is nothing to preserve. + // 4) Constructor that emits initializers is updated. + // We create syntax map even if it's not necessary: if any data member initializers are active/contain lambdas. + // Since initializers are usually simple the map should not be large enough to make it worth optimizing it away. + if (hasActiveStatement || + newHasStateMachineSuspensionPoint || + newBodyHasLambdas || + IsConstructorWithMemberInitializers(newDeclaration) || + IsDeclarationWithInitializer(oldDeclaration) || + IsDeclarationWithInitializer(newDeclaration)) + { + syntaxMap = CreateSyntaxMap(map.Reverse); + } for (var i = 0; i < activeNodes.Length; i++) { @@ -1130,7 +1116,8 @@ private void AnalyzeChangedMemberBody( // rude edit: internal active statement deleted diagnostics.Add( new RudeEditDiagnostic(isNonLeaf ? RudeEditKind.DeleteActiveStatement : RudeEditKind.PartiallyExecutedActiveStatementDelete, - GetDeletedNodeDiagnosticSpan(match.Matches, oldStatementSyntax))); + GetDeletedNodeDiagnosticSpan(match.Matches, oldStatementSyntax), + arguments: new[] { FeaturesResources.code })); } } @@ -1954,6 +1941,11 @@ private static int IndexOfEquivalent(SyntaxNode newNode, List + /// Top-level edit script does not contain edits for a member if only trivia changed in its body. + /// Members that are unchanged but their location in the file changes are not considered updated. + /// This method calculates line and trivia edits for both of these cases. + /// private void AnalyzeTrivia( SourceText oldSource, SourceText newSource, @@ -1964,6 +1956,8 @@ private void AnalyzeTrivia( [Out] ArrayBuilder diagnostics, CancellationToken cancellationToken) { + Debug.Assert(diagnostics.Count == 0); + foreach (var (oldNode, newNode) in topMatch.Matches) { cancellationToken.ThrowIfCancellationRequested(); @@ -1981,10 +1975,14 @@ private void AnalyzeTrivia( continue; } - // The old node matches the new node and body tokens are available for the new node - // hence the old node must have body tokens as well. + // A (rude) edit could have been made that changes whether the node may contain active statements, + // so although the nodes match they might not have the same active tokens. + // E.g. field declaration changed to const field declaration. var oldTokens = TryGetActiveTokens(oldNode); - RoslynDebug.Assert(oldTokens != null); + if (oldTokens == null) + { + continue; + } var newTokensEnum = newTokens.GetEnumerator(); var oldTokensEnum = oldTokens.GetEnumerator(); @@ -2157,7 +2155,10 @@ private readonly struct ConstructorEdit { public readonly INamedTypeSymbol OldType; - // { new field/property initializer or constructor declaration -> syntax map } + /// + /// Contains syntax maps for all changed data member initializers or constructor declarations (of constructors emitting initializers) + /// in the currently analyzed document. The key is the declaration of the member. + /// public readonly Dictionary?> ChangedDeclarations; public ConstructorEdit(INamedTypeSymbol oldType) @@ -2167,414 +2168,606 @@ public ConstructorEdit(INamedTypeSymbol oldType) } } - private void AnalyzeSemantics( + private async Task> AnalyzeSemanticsAsync( EditScript editScript, IReadOnlyDictionary editMap, SourceText oldText, + SourceText newText, ImmutableArray oldActiveStatements, + ImmutableArray newActiveStatementSpans, IReadOnlyList<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, - IReadOnlyList updatedMembers, - SemanticModel oldModel, - SemanticModel newModel, - [Out] ArrayBuilder semanticEdits, - [Out] ArrayBuilder diagnostics, + Project oldProject, + Document? oldDocument, + Document newDocument, + ArrayBuilder diagnostics, + ImmutableArray.Builder newActiveStatements, + ImmutableArray>.Builder newExceptionRegions, CancellationToken cancellationToken) { + if (editScript.Edits.Length == 0 && triviaEdits.Count == 0) + { + return ImmutableArray.Empty; + } + // { new type -> constructor update } - Dictionary? instanceConstructorEdits = null; - Dictionary? staticConstructorEdits = null; + PooledDictionary? instanceConstructorEdits = null; + PooledDictionary? staticConstructorEdits = null; + + var oldModel = (oldDocument != null) ? await oldDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false) : null; + var newModel = await newDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var oldCompilation = oldModel?.Compilation ?? await oldProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var newCompilation = newModel.Compilation; - INamedTypeSymbol? lazyLayoutAttribute = null; - var newSymbolsWithEdit = new HashSet(); - var updatedMemberIndex = 0; - for (var i = 0; i < editScript.Edits.Length; i++) + using var _1 = PooledHashSet.GetInstance(out var processedSymbols); + using var _2 = ArrayBuilder.GetInstance(out var semanticEdits); + + try { - cancellationToken.ThrowIfCancellationRequested(); + INamedTypeSymbol? lazyLayoutAttribute = null; + for (var i = 0; i < editScript.Edits.Length; i++) + { + cancellationToken.ThrowIfCancellationRequested(); - var edit = editScript.Edits[i]; + var edit = editScript.Edits[i]; - ISymbol? oldSymbol; - ISymbol? newSymbol; - Func? syntaxMap; - SemanticEditKind editKind; + ISymbol? oldSymbol; + ISymbol? newSymbol; + SymbolKey? lazySymbolKey = null; + Func? syntaxMap; + SemanticEditKind editKind; - switch (edit.Kind) - { - case EditKind.Move: - // Move is always a Rude Edit. - throw ExceptionUtilities.UnexpectedValue(edit.Kind); + switch (edit.Kind) + { + case EditKind.Move: + // Move is either a Rude Edit and already reported in syntax analysis, or has no semantic effect. + // For example, in VB we allow move from field multi-declaration. + // "Dim a, b As Integer" -> "Dim a As Integer" (update) and "Dim b As Integer" (move) + continue; - case EditKind.Delete: - { - if (HasParentEdit(editMap, edit)) - { - continue; - } + case EditKind.Reorder: + // Currently we don't do any semantic checks for reordering + // and we don't need to report them to the compiler either. + // Consider: Currently symbol ordering changes are not reflected in metadata (Reflection will report original order). - oldSymbol = GetSymbolForEdit(oldModel, edit.OldNode, edit.Kind, editMap, cancellationToken); - if (oldSymbol == null) - { - continue; - } + // Consider: Reordering of fields is not allowed since it changes the layout of the type. + // This ordering should however not matter unless the type has explicit layout so we might want to allow it. + // We do not check changes to the order if they occur across multiple documents (the containing type is partial). + Debug.Assert(!IsDeclarationWithInitializer(edit.OldNode) && !IsDeclarationWithInitializer(edit.NewNode)); + continue; - // The only member that is allowed to be deleted is a parameterless constructor. - // For any other member a rude edit is reported earlier during syntax edit classification. - // Deleting a parameterless constructor needs special handling. - // If the new type has a parameterless ctor of the same accessibility then UPDATE. - // Error otherwise. + case EditKind.Delete: + { + Contract.ThrowIfNull(oldModel); - Contract.ThrowIfNull(AsParameterlessConstructor(oldSymbol)); + oldSymbol = GetSymbolForEdit(oldModel, edit.OldNode, edit.Kind, editMap, out var oldIsAmbiguous, cancellationToken); + if (oldSymbol == null || oldIsAmbiguous || !processedSymbols.Add(oldSymbol)) + { + // Node doesn't represent a symbol or it represents multiple symbols and the semantic delete + // will be issued for node that represents the specific symbol. + continue; + } - var oldTypeSyntax = TryGetContainingTypeDeclaration(edit.OldNode); - Contract.ThrowIfNull(oldTypeSyntax); + var hasActiveStatement = TryGetOverlappingActiveStatements(oldText, edit.OldNode.Span, oldActiveStatements, out var start, out var end); - var newType = TryGetPartnerType(oldTypeSyntax, editScript.Match, newModel, cancellationToken); + // TODO: if the member isn't a field/property we should return empty span. + // We need to adjust the tracking span design and UpdateUneditedSpans to account for such empty spans. + if (hasActiveStatement) + { + var newSpan = IsDeclarationWithInitializer(edit.OldNode) ? + GetDeletedNodeActiveSpan(editScript.Match.Matches, edit.OldNode) : + GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, edit.OldNode); - // If the type has been deleted we would have reported a rude edit based on syntax analysis and not get here. - Contract.ThrowIfNull(newType); + for (var index = start; index < end; index++) + { + // TODO: VB field multi-initializers break this + // Debug.Assert(newActiveStatements[i] == default(LinePositionSpan)); - newSymbol = TryGetParameterlessConstructor(newType, oldSymbol.IsStatic); + newActiveStatements[index] = oldActiveStatements[index].WithSpan(newText.Lines.GetLinePositionSpan(newSpan)); + newExceptionRegions[index] = ImmutableArray.Create(); + } + } - // If the new constructor is explicitly declared then it must be in another part of a partial type declaration. - // A type can't have more than one parameterless constructor declaration. The current edit is deleting it. - // The new type symbol has a parameterless constructor. Therefore this must be either implicitly declared - // or it is now declared in another part of a partial type declaration. The former case results in - // an update of the constructor symbol. The latter is skipped since the update edit of the constructor will be - // created when the part where the declaration is inserted is analyzed. - if (newSymbol != null && !newSymbol.IsImplicitlyDeclared) - { - continue; - } + syntaxMap = null; + editKind = SemanticEditKind.Delete; - if (newSymbol == null || newSymbol.DeclaredAccessibility != oldSymbol.DeclaredAccessibility) - { - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.Delete, - GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, edit.OldNode), - edit.OldNode, - new[] { GetDisplayName(edit.OldNode, EditKind.Delete) })); + // Check if the declaration has been moved from one document to another. + var symbolKey = SymbolKey.Create(oldSymbol, cancellationToken); + lazySymbolKey = symbolKey; - continue; - } + // Ignore ambiguous resolution result - it may happen if there are semantic errors in the compilation. + newSymbol = symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; + if (newSymbol != null && !(newSymbol is IMethodSymbol newMethod && newMethod.IsPartialDefinition)) + { + // Symbol has actually not been deleted but rather moved to another document, another partial type declaration + // or replaced with an implicitly generated one (e.g. parameterless constructor, auto-generated record methods, etc.) + + // Report rude edit if the deleted code contains active statements. + // TODO (https://github.com/dotnet/roslyn/issues/51177): + // Only report rude edit when replacing member with an implicit one if it has an active statement. + // We might be able to support moving active members but we would need to + // 1) Move AnalyzeChangedMemberBody from Insert to Delete + // 2) Handle active statements that moved to a different document in ActiveStatementTrackingService + // 3) The debugger's ManagedActiveStatementUpdate might need another field indicating the source file path. + if (hasActiveStatement) + { + ReportDeletedMemberRudeEdit(diagnostics, editScript, edit.OldNode, oldSymbol, RudeEditKind.DeleteActiveStatement); + continue; + } - editKind = SemanticEditKind.Update; - syntaxMap = null; - } + if (!newSymbol.IsImplicitlyDeclared) + { + // Ignore the delete. The new symbol is explicitly declared and thus there will be an insert edit that will issue a semantic update. + continue; + } - break; + // can't change visibility: + if (newSymbol.DeclaredAccessibility != oldSymbol.DeclaredAccessibility) + { + ReportDeletedMemberRudeEdit(diagnostics, editScript, edit.OldNode, oldSymbol, RudeEditKind.ChangingVisibility); + continue; + } - case EditKind.Reorder: - // Currently we don't do any semantic checks for reordering - // and we don't need to report them to the compiler either. + // If a constructor is deleted and replaced by an implicit one the update needs to aggregate updates to all data member initializers. + if (IsConstructorWithMemberInitializers(edit.OldNode)) + { + processedSymbols.Remove(oldSymbol); + DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + continue; + } - // Reordering of fields is not allowed since it changes the layout of the type. - Debug.Assert(!IsDeclarationWithInitializer(edit.OldNode) && !IsDeclarationWithInitializer(edit.NewNode)); - continue; + // there is no insert edit for an implicit declaration, therefore we need to issue an update: + editKind = SemanticEditKind.Update; + } + else + { + // Check if the symbol being deleted is a member of a type or associated with a property or event that's also being deleted. + // If so, skip the member deletion and only report the containing symbol deletion. + var oldContainingSymbol = (oldSymbol as IMethodSymbol)?.AssociatedSymbol ?? oldSymbol.ContainingType; + if (oldContainingSymbol != null) + { + var containingSymbolKey = SymbolKey.Create(oldContainingSymbol, cancellationToken); + var newContatiningSymbol = containingSymbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; + if (newContatiningSymbol == null) + { + continue; + } + } - case EditKind.Insert: - { - editKind = SemanticEditKind.Insert; + // deleting symbol is not allowed + var diagnosticSpan = GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, edit.OldNode); - var newTypeSyntax = TryGetContainingTypeDeclaration(edit.NewNode); + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.Delete, + diagnosticSpan, + edit.OldNode, + new[] + { + string.Format(FeaturesResources.member_kind_and_name, + GetDisplayName(edit.OldNode, EditKind.Delete), + oldSymbol.ToDisplayString(diagnosticSpan.IsEmpty ? s_fullyQualifiedMemberDisplayFormat : s_unqualifiedMemberDisplayFormat)) + })); - if (newTypeSyntax != null && HasEdit(editMap, newTypeSyntax, EditKind.Insert)) - { - // inserting into a new type - continue; + continue; + } } - syntaxMap = null; - oldSymbol = null; - newSymbol = GetSymbolForEdit(newModel, edit.NewNode, edit.Kind, editMap, cancellationToken); + break; - if (newSymbol == null) + case EditKind.Insert: { - // node doesn't represent a symbol - continue; - } + Contract.ThrowIfNull(newModel); - // TODO: scripting - // inserting a top-level member/type - if (newTypeSyntax == null) - { - break; - } + syntaxMap = null; + oldSymbol = null; + newSymbol = GetSymbolForEdit(newModel, edit.NewNode, edit.Kind, editMap, out var newIsAmbiguous, cancellationToken); + if (newSymbol == null || newIsAmbiguous || !processedSymbols.Add(newSymbol)) + { + // Node doesn't represent a symbol or it represents multiple symbols and the semantic insert + // will be issued for node that represents the specific symbol. + continue; + } - var newType = (INamedTypeSymbol)newModel.GetRequiredDeclaredSymbol(newTypeSyntax, cancellationToken); - var oldType = TryGetPartnerType(newTypeSyntax, editScript.Match, oldModel, cancellationToken); - - // There has to be a matching old type syntax since the containing type hasn't been inserted. - Contract.ThrowIfNull(oldType); - Contract.ThrowIfNull(newType); - - ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol); - ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, edit.NewNode, newModel, ref lazyLayoutAttribute); - - // Inserting a parameterless constructor needs special handling: - // 1) static ctor - // a) old type has an implicit static ctor - // UPDATE of the implicit static ctor - // b) otherwise - // INSERT of a static parameterless ctor - // - // 2) public instance ctor - // a) old type has an implicit instance ctor - // UPDATE of the implicit instance ctor - // b) otherwise - // ERROR: adding a non-private member - // 3) non-public instance ctor - // a) old type has an implicit instance ctor - // ERROR: changing visibility of the ctor - // b) otherwise - // INSERT of an instance parameterless ctor - - var newCtor = AsParameterlessConstructor(newSymbol); - if (newCtor != null) - { - oldSymbol = TryGetParameterlessConstructor(oldType, newSymbol.IsStatic); + editKind = SemanticEditKind.Insert; + INamedTypeSymbol? oldContainingType; + var newContainingType = newSymbol.ContainingType; + + // Check if the declaration has been moved from one document to another. + var symbolKey = SymbolKey.Create(newSymbol, cancellationToken); + lazySymbolKey = symbolKey; - if (newCtor.IsStatic) + // Ignore ambiguous resolution result - it may happen if there are semantic errors in the compilation. + oldSymbol = symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; + if (oldSymbol != null) { - if (oldSymbol != null) + // Symbol has actually not been inserted but rather moved between documents or partial type declarations, + // or is replacing an implicitly generated one (e.g. parameterless constructor, auto-generated record methods, etc.) + oldContainingType = oldSymbol.ContainingType; + + if (oldSymbol.IsImplicitlyDeclared) { - editKind = SemanticEditKind.Update; + // Replace implicit declaration with an explicit one. + + if (oldSymbol.DeclaredAccessibility != newSymbol.DeclaredAccessibility) + { + diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.ChangingVisibility, + GetDiagnosticSpan(edit.NewNode, edit.Kind), + arguments: new[] { GetDisplayName(edit.NewNode, edit.Kind) })); + + continue; + } } - } - else if (oldSymbol != null) - { - if (oldSymbol.DeclaredAccessibility != newCtor.DeclaredAccessibility) + else if (newSymbol is IFieldSymbol { ContainingType: { TypeKind: TypeKind.Enum } }) + { + // Skip enum field declarations. Enums can't be partial their fields must be inserted at the same time as the enum itself. + continue; + } + else if (newSymbol is INamedTypeSymbol { TypeKind: not (TypeKind.Delegate or TypeKind.Enum) } newTypeSymbol) { - // changing visibility of a member - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.ChangingConstructorVisibility, - GetDiagnosticSpan(edit.NewNode, EditKind.Insert))); + // The old symbol must be named type as well since we resolved it via symbol key above. + var oldTypeSymbol = (INamedTypeSymbol)oldSymbol; + + // The types have multiple partial declaration parts, each can contribute attributes and base types. + // All have to declare the same type parameters, but each can add different attributes to them. + // Only one can contribute generic type parameter constraints. + // We collect all these entities and require them to be unchanged. + ReportTypeDeclarationInsertDeleteRudeEdits(diagnostics, oldTypeSymbol, newTypeSymbol, edit.NewNode, cancellationToken); + + continue; } - else + else if (oldSymbol.DeclaringSyntaxReferences.Length == 1 && newSymbol.DeclaringSyntaxReferences.Length == 1) { + // We ignore partial method definition parts when processing edits (GetSymbolForEdit). + // The only declaration in compilation without syntax errors that can have multiple declaring references is a type declaration. + // We can therefore ignore any symbols that have more than one declaration. + ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, edit.NewNode, newModel, ref lazyLayoutAttribute); + + // Compare the old declaration syntax of the symbol with its new declaration and report rude edits + // if it changed in any way that's not allowed. + var oldNode = GetSymbolDeclarationSyntax(oldSymbol.DeclaringSyntaxReferences[0], cancellationToken); + var newNode = edit.NewNode; + + ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldNode, newNode); + + var oldBody = TryGetDeclarationBody(oldNode); + var newBody = TryGetDeclarationBody(newNode); + if (oldBody == null && newBody == null) + { + continue; + } + + if (oldBody != null) + { + // The old symbol's declaration syntax may be located in a different document than the old version of the current document. + var oldSyntaxDocument = oldProject.Solution.GetRequiredDocument(oldNode.SyntaxTree); + var oldSyntaxModel = await oldSyntaxDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var oldSyntaxText = await oldSyntaxDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + + // Skip analysis of active statements. We already report rude edit for removal of code containing + // active statements in the old declaration and don't currently support moving active statements. + AnalyzeChangedMemberBody( + oldNode, + newNode, + oldBody, + newBody, + oldSyntaxText, + newText, + oldSyntaxModel, + newModel, + oldSymbol, + newSymbol, + oldActiveStatements: ImmutableArray.Empty, + newActiveStatementSpans: ImmutableArray.Empty, + newActiveStatements, + newExceptionRegions, + diagnostics, + out syntaxMap, + cancellationToken); + } + + // If a constructor changes from including initializers to not including initializers + // we don't need to aggregate syntax map from all initializers for the constructor update semantic edit. + if (IsConstructorWithMemberInitializers(newNode) || + IsDeclarationWithInitializer(oldNode) || + IsDeclarationWithInitializer(newNode)) + { + processedSymbols.Remove(newSymbol); + DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newNode, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + + // Don't add a separate semantic edit. + // Updates of data members with initializers and constructors that emit initializers will be aggregated and added later. + continue; + } + editKind = SemanticEditKind.Update; } } - } - - var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.NewNode); - if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(edit.NewNode)) - { - if (DeferConstructorEdit( - oldType, - newType, - editKind, - edit.NewNode, - newSymbol, - newModel, - isConstructorWithMemberInitializers, - ref syntaxMap, - ref instanceConstructorEdits, - ref staticConstructorEdits, - diagnostics, - cancellationToken)) + else if (newSymbol.ContainingType != null) { - if (newSymbol.Kind == SymbolKind.Method) + // The edit actually adds a new symbol into an existing or a new type. + + // If the symbol is an accessor and the containing property/indexer/event declaration has also been inserted skip + // the insert of the accessor as it will be inserted by the property/indexer/event. + var newAssociatedMemberDeclaration = TryGetAssociatedMemberDeclaration(edit.NewNode); + if (newAssociatedMemberDeclaration != null && HasEdit(editMap, newAssociatedMemberDeclaration, EditKind.Insert)) { - // Don't add a separate semantic edit for a field/property with an initializer. - // All edits of initializers will be aggregated to edits of constructors where these initializers are emitted. continue; } - else + + var containingSymbolKey = SymbolKey.Create(newContainingType, cancellationToken); + oldContainingType = containingSymbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol as INamedTypeSymbol; + + // Check rude edits for each member even if it is inserted into a new type. + ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, edit.NewNode, insertingIntoExistingContainingType: oldContainingType != null); + + if (oldContainingType == null) { - // A semantic edit to create the field/property is gonna be added. - Contract.ThrowIfFalse(editKind == SemanticEditKind.Insert); + // Insertion of a new symbol into a new type. + // We'll produce a single insert edit for the entire type. + continue; } + + // Report rude edits for changes to data member changes of a type with an explicit layout. + // We disallow moving a data member of a partial type with explicit layout even when it actually does not change the layout. + // We could compare the exact order of the members but the scenario is unlikely to occur. + ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, edit.NewNode, newModel, ref lazyLayoutAttribute); } - } - } + else + { + // adds a new top-level type + Contract.ThrowIfFalse(newSymbol is INamedTypeSymbol); - break; + oldContainingType = null; + ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, edit.NewNode, insertingIntoExistingContainingType: false); + } - case EditKind.Update: - { - editKind = SemanticEditKind.Update; + var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.NewNode); + if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(edit.NewNode)) + { + Contract.ThrowIfNull(newContainingType); + Contract.ThrowIfNull(oldContainingType); + + // TODO (bug https://github.com/dotnet/roslyn/issues/2504) + if (isConstructorWithMemberInitializers && + editKind == SemanticEditKind.Insert && + IsPartial(newContainingType) && + HasMemberInitializerContainingLambda(oldContainingType, newSymbol.IsStatic, cancellationToken)) + { + // rude edit: Adding a constructor to a type with a field or property initializer that contains an anonymous function + diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, GetDiagnosticSpan(edit.NewNode, EditKind.Insert))); + break; + } - // An updated member info was added for a subset of edits in the order of the edits. - // Fetch the next info if it matches the current edit ordinal. - UpdatedMemberInfo? updatedMemberOpt; - if (updatedMemberIndex < updatedMembers.Count && updatedMembers[updatedMemberIndex].EditOrdinal == i) - { - updatedMemberOpt = updatedMembers[updatedMemberIndex++]; - } - else - { - updatedMemberOpt = null; + DeferConstructorEdit(oldContainingType, newContainingType, edit.NewNode, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + + if (isConstructorWithMemberInitializers || editKind == SemanticEditKind.Update) + { + processedSymbols.Remove(newSymbol); + + // Don't add a separate semantic edit. + // Edits of data members with initializers and constructors that emit initializers will be aggregated and added later. + continue; + } + + // A semantic edit to create the field/property is gonna be added. + Contract.ThrowIfFalse(editKind == SemanticEditKind.Insert); + } } - newSymbol = GetSymbolForEdit(newModel, edit.NewNode, edit.Kind, editMap, cancellationToken); - if (newSymbol == null) + break; + + case EditKind.Update: { - // node doesn't represent a symbol - Contract.ThrowIfTrue(updatedMemberOpt.HasValue); - continue; - } + Contract.ThrowIfNull(oldModel); + Contract.ThrowIfNull(newModel); - oldSymbol = GetSymbolForEdit(oldModel, edit.OldNode, edit.Kind, editMap, cancellationToken); - Contract.ThrowIfNull(oldSymbol); + newSymbol = GetSymbolForEdit(newModel, edit.NewNode, edit.Kind, editMap, out var newIsAmbiguous, cancellationToken); + if (newSymbol == null || !processedSymbols.Add(newSymbol)) + { + // node doesn't represent a symbol or the symbol has already been processed + continue; + } - var oldContainingType = oldSymbol.ContainingType; - var newContainingType = newSymbol.ContainingType; + editKind = SemanticEditKind.Update; + syntaxMap = null; + oldSymbol = GetSymbolForEdit(oldModel, edit.OldNode, edit.Kind, editMap, out var oldIsAmbiguous, cancellationToken); + if (oldSymbol == null) + { + // May happen when the old node represents partial method changed from a definition to an implementation (adding a body). + // This is already reported as rude edit. + continue; + } - if (updatedMemberOpt.HasValue) - { - var updatedMember = updatedMemberOpt.Value; - - ReportStateMachineRudeEdits(oldModel.Compilation, updatedMember, oldSymbol, diagnostics); - - ReportLambdaAndClosureRudeEdits( - oldModel, - updatedMember.OldBody, - newModel, - updatedMember.NewBody, - newSymbol, - updatedMember.ActiveOrMatchedLambdas, - updatedMember.Map, - diagnostics, - out var newBodyHasLambdas, - cancellationToken); - - // We need to provide syntax map to the compiler if - // 1) The new member has a active statement - // The values of local variables declared or synthesized in the method have to be preserved. - // 2) The new member generates a state machine - // In case the state machine is suspended we need to preserve variables. - // 3) The new member contains lambdas - // We need to map new lambdas in the method to the matching old ones. - // If the old method has lambdas but the new one doesn't there is nothing to preserve. - if (updatedMember.HasActiveStatement || updatedMember.NewHasStateMachineSuspensionPoint || newBodyHasLambdas) + GetUpdatedDeclarationBodies(edit.OldNode, edit.NewNode, out var oldBody, out var newBody); + if (oldBody != null) { - syntaxMap = CreateSyntaxMap(updatedMember.Map.Reverse); + AnalyzeChangedMemberBody( + edit.OldNode, + edit.NewNode, + oldBody, + newBody, + oldText, + newText, + oldModel, + newModel, + oldSymbol, + newSymbol, + oldActiveStatements, + newActiveStatementSpans, + newActiveStatements, + newExceptionRegions, + diagnostics, + out syntaxMap, + cancellationToken); } - else + + // If a constructor changes from including initializers to not including initializers + // we don't need to aggregate syntax map from all initializers for the constructor update semantic edit. + if (IsConstructorWithMemberInitializers(edit.NewNode) || + IsDeclarationWithInitializer(edit.OldNode) || + IsDeclarationWithInitializer(edit.NewNode)) { - syntaxMap = null; + processedSymbols.Remove(newSymbol); + DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, edit.NewNode, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + + // Don't add a separate semantic edit. + // Updates of data members with initializers and constructors that emit initializers will be aggregated and added later. + continue; } - } - else - { - syntaxMap = null; - } - // If a constructor changes from including initializers to not including initializers - // we don't need to aggregate syntax map from all initializers for the constructor update semantic edit. - var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.NewNode); - if (isConstructorWithMemberInitializers || - IsDeclarationWithInitializer(edit.OldNode) || - IsDeclarationWithInitializer(edit.NewNode)) - { - if (DeferConstructorEdit( - oldContainingType, - newContainingType, - editKind, - edit.NewNode, - newSymbol, - newModel, - isConstructorWithMemberInitializers, - ref syntaxMap, - ref instanceConstructorEdits, - ref staticConstructorEdits, - diagnostics, - cancellationToken)) + // Do not create a semantic update if the edit is not specific to a single symbol. + // The update might still affect a constructor update processed above. + if (oldIsAmbiguous || newIsAmbiguous) { - // Don't add a separate semantic edit for a field/property with an initializer. - // All edits of initializers will be aggregated to edits of constructors where these initializers are emitted. continue; } } + + break; + + default: + throw ExceptionUtilities.UnexpectedValue(edit.Kind); + } + + Contract.ThrowIfFalse(editKind is SemanticEditKind.Update or SemanticEditKind.Insert); + + if (editKind == SemanticEditKind.Update) + { + // The only update to the type itself that's supported is an addition or removal of the partial modifier, + // which does not have impact on the emitted type metadata. + if (newSymbol is INamedTypeSymbol) + { + continue; } - break; + // The property itself is being updated. Currently we do not allow any modifiers or attributes to be updated, + // so the only case when this happens is in C# for a property/indexer that has an expression body. + // The symbol that's actually being updated is the getter. + // TODO: This will need to be revisited in https://github.com/dotnet/roslyn/issues/48628 + if (newSymbol is IPropertySymbol { GetMethod: var propertyGetter and not null }) + { + newSymbol = propertyGetter; + lazySymbolKey = null; + } + } - default: - throw ExceptionUtilities.UnexpectedValue(edit.Kind); + lazySymbolKey ??= SymbolKey.Create(newSymbol, cancellationToken); + + // Edits in data member initializers and constructors are deferred, edits of other members (even on partial types) + // do not need merging accross partial type declarations. + semanticEdits.Add(new SemanticEditInfo(editKind, lazySymbolKey.Value, syntaxMap, partialType: null)); } - semanticEdits.Add(new SemanticEdit(editKind, oldSymbol, newSymbol, syntaxMap, preserveLocalVariables: syntaxMap != null)); - newSymbolsWithEdit.Add(newSymbol); - } + foreach (var (oldNode, newNode) in triviaEdits) + { + Contract.ThrowIfNull(oldModel); + Contract.ThrowIfNull(newModel); - foreach (var (oldNode, newNode) in triviaEdits) - { - var oldSymbol = GetSymbolForEdit(oldModel, oldNode, EditKind.Update, editMap, cancellationToken); - var newSymbol = GetSymbolForEdit(newModel, newNode, EditKind.Update, editMap, cancellationToken); + var newSymbol = GetSymbolForEdit(newModel, newNode, EditKind.Update, editMap, out var newIsAmbiguous, cancellationToken); - // Trivia edits are only calculated for members bodies and each member has a symbol. - RoslynDebug.Assert(oldSymbol != null); - RoslynDebug.Assert(newSymbol != null); + // Trivia edits are only calculated for member bodies and each member has a symbol. + Contract.ThrowIfNull(newSymbol); - var oldContainingType = oldSymbol.ContainingType; - var newContainingType = newSymbol.ContainingType; + if (!processedSymbols.Add(newSymbol)) + { + // symbol already processed + continue; + } - // We need to provide syntax map to the compiler if the member is active (see member update above): - var isActiveMember = - TryGetOverlappingActiveStatements(oldText, oldNode.Span, oldActiveStatements, out var start, out var end) || - IsStateMachineMethod(oldNode) || - ContainsLambda(oldNode); + var oldSymbol = GetSymbolForEdit(oldModel, oldNode, EditKind.Update, editMap, out var oldIsAmbiguous, cancellationToken); + Contract.ThrowIfNull(oldSymbol); - var syntaxMap = isActiveMember ? CreateSyntaxMapForEquivalentNodes(oldNode, newNode) : null; + var oldContainingType = oldSymbol.ContainingType; + var newContainingType = newSymbol.ContainingType; - // only trivia changed: - Debug.Assert(IsConstructorWithMemberInitializers(oldNode) == IsConstructorWithMemberInitializers(newNode)); - Debug.Assert(IsDeclarationWithInitializer(oldNode) == IsDeclarationWithInitializer(newNode)); + // We need to provide syntax map to the compiler if the member is active (see member update above): + var isActiveMember = + TryGetOverlappingActiveStatements(oldText, oldNode.Span, oldActiveStatements, out var start, out var end) || + IsStateMachineMethod(oldNode) || + ContainsLambda(oldNode); - var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newNode); - if (isConstructorWithMemberInitializers || - IsDeclarationWithInitializer(newNode)) - { - if (DeferConstructorEdit( - oldContainingType, - newContainingType, - SemanticEditKind.Update, - newNode, - newSymbol, - newModel, - isConstructorWithMemberInitializers, - ref syntaxMap, - ref instanceConstructorEdits, - ref staticConstructorEdits, - diagnostics, - cancellationToken)) + var syntaxMap = isActiveMember ? CreateSyntaxMapForEquivalentNodes(oldNode, newNode) : null; + + // only trivia changed: + Contract.ThrowIfFalse(IsConstructorWithMemberInitializers(oldNode) == IsConstructorWithMemberInitializers(newNode)); + Contract.ThrowIfFalse(IsDeclarationWithInitializer(oldNode) == IsDeclarationWithInitializer(newNode)); + + if (IsConstructorWithMemberInitializers(newNode) || IsDeclarationWithInitializer(newNode)) { - // Don't add a separate semantic edit for a field/property with an initializer. - // All edits of initializers will be aggregated to edits of constructors where these initializers are emitted. + // TODO: only create syntax map if any field initializers are active/contain lambdas or this is a partial type + syntaxMap ??= CreateSyntaxMapForEquivalentNodes(oldNode, newNode); + + processedSymbols.Remove(newSymbol); + DeferConstructorEdit(oldContainingType, newContainingType, newNode, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + + // Don't add a separate semantic edit. + // Updates of data members with initializers and constructors that emit initializers will be aggregated and added later. continue; } + + // Do not create a semantic update if the edit is not specific to a single symbol. + // The update might still affect a constructor update processed above. + if (oldIsAmbiguous || newIsAmbiguous) + { + continue; + } + + // Edits in data member initializers and constructors are deferred, edits of other members (even on partial types) + // do not need merging accross partial type declarations. + var symbolKey = SymbolKey.Create(newSymbol, cancellationToken); + semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, partialType: null)); } - semanticEdits.Add(new SemanticEdit(SemanticEditKind.Update, oldSymbol, newSymbol, syntaxMap, isActiveMember)); - newSymbolsWithEdit.Add(newSymbol); - } + if (instanceConstructorEdits != null) + { + AddConstructorEdits( + instanceConstructorEdits, + editScript.Match, + oldModel, + oldCompilation, + processedSymbols, + isStatic: false, + semanticEdits, + diagnostics, + cancellationToken); + } - if (instanceConstructorEdits != null) - { - AddConstructorEdits( - instanceConstructorEdits, - editScript.Match, - oldModel, - newSymbolsWithEdit, - isStatic: false, - semanticEdits: semanticEdits, - diagnostics: diagnostics, - cancellationToken: cancellationToken); + if (staticConstructorEdits != null) + { + AddConstructorEdits( + staticConstructorEdits, + editScript.Match, + oldModel, + oldCompilation, + processedSymbols, + isStatic: true, + semanticEdits, + diagnostics, + cancellationToken); + } } - - if (staticConstructorEdits != null) + finally { - AddConstructorEdits( - staticConstructorEdits, - editScript.Match, - oldModel, - newSymbolsWithEdit, - isStatic: true, - semanticEdits: semanticEdits, - diagnostics: diagnostics, - cancellationToken: cancellationToken); + instanceConstructorEdits?.Free(); + staticConstructorEdits?.Free(); } + + return semanticEdits.ToImmutable(); + } + + private void ReportDeletedMemberRudeEdit( + ArrayBuilder diagnostics, + EditScript editScript, + SyntaxNode oldNode, + ISymbol oldSymbol, + RudeEditKind rudeEditKind) + { + diagnostics.Add(new RudeEditDiagnostic( + rudeEditKind, + GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, oldNode), + arguments: new[] + { + string.Format(FeaturesResources.member_kind_and_name, GetDisplayName(oldNode, EditKind.Delete), oldSymbol.ToDisplayString(s_unqualifiedMemberDisplayFormat)) + })); } #region Type Layout Update Validation @@ -2684,32 +2877,8 @@ private static bool HasExplicitOrSequentialLayout( #endregion - private static INamedTypeSymbol? TryGetPartnerType(SyntaxNode typeSyntax, Match topMatch, SemanticModel partnerModel, CancellationToken cancellationToken) - { - SyntaxNode partner; - if (topMatch.OldRoot.SyntaxTree == typeSyntax.SyntaxTree) - { - _ = topMatch.TryGetNewNode(typeSyntax, out partner); - } - else - { - _ = topMatch.TryGetOldNode(typeSyntax, out partner); - } - - if (partner == null) - { - return null; - } - - Debug.Assert(partner.SyntaxTree == partnerModel.SyntaxTree); - - return (INamedTypeSymbol?)partnerModel.GetDeclaredSymbol(partner, cancellationToken); - } - private Func CreateSyntaxMapForEquivalentNodes(SyntaxNode oldRoot, SyntaxNode newRoot) { - Debug.Assert(AreEquivalent(newRoot, oldRoot)); - return newNode => newRoot.FullSpan.Contains(newNode.SpanStart) ? FindPartner(newRoot, oldRoot, newNode) : null; } @@ -2717,15 +2886,6 @@ private static bool HasExplicitOrSequentialLayout( private static Func CreateSyntaxMap(IReadOnlyDictionary reverseMap) => newNode => reverseMap.TryGetValue(newNode, out var oldNode) ? oldNode : null; - private Func CreateSyntaxMapForPartialTypeConstructor( - INamedTypeSymbol oldType, - INamedTypeSymbol newType, - SemanticModel newModel, - Func? ctorSyntaxMap) - { - return newNode => ctorSyntaxMap?.Invoke(newNode) ?? FindPartnerInMemberInitializer(newModel, newType, newNode, oldType, default); - } - private Func? CreateAggregateSyntaxMap( IReadOnlyDictionary reverseTopMatches, IReadOnlyDictionary?> changedDeclarations) @@ -2736,7 +2896,7 @@ private static bool HasExplicitOrSequentialLayout( var newDeclaration = FindMemberDeclaration(root: null, newNode); // the syntax map is only used for nodes that are contained in a declaration - RoslynDebug.Assert(newDeclaration != null); + Contract.ThrowIfNull(newDeclaration); // The node is in a field, property or constructor declaration that has been changed: if (changedDeclarations.TryGetValue(newDeclaration, out var syntaxMap)) @@ -2759,86 +2919,26 @@ private static bool HasExplicitOrSequentialLayout( #region Constructors and Initializers - private static IMethodSymbol? AsParameterlessConstructor(ISymbol symbol) - { - if (symbol.Kind != SymbolKind.Method) - { - return null; - } - - var method = (IMethodSymbol)symbol; - var kind = method.MethodKind; - if (kind != MethodKind.Constructor && kind != MethodKind.StaticConstructor) - { - return null; - } - - return method.Parameters.Length == 0 ? method : null; - } - /// /// Called when a body of a constructor or an initializer of a member is updated or inserted. /// - private bool DeferConstructorEdit( + private static void DeferConstructorEdit( INamedTypeSymbol oldType, INamedTypeSymbol newType, - SemanticEditKind editKind, - SyntaxNode newDeclaration, - ISymbol newSymbol, - SemanticModel newModel, - bool isConstructor, - ref Func? syntaxMap, - ref Dictionary? instanceConstructorEdits, - ref Dictionary? staticConstructorEdits, - [Out] ArrayBuilder diagnostics, - CancellationToken cancellationToken) + SyntaxNode? newDeclaration, + Func? syntaxMap, + bool isStatic, + ref PooledDictionary? instanceConstructorEdits, + ref PooledDictionary? staticConstructorEdits) { - if (IsPartial(newType)) - { - // Since we don't calculate match across partial declarations we need to disallow - // adding and updating fields/properties with initializers of a partial type declaration. - // Assuming this restriction we can allow editing all constructors of partial types. - // The ones that include initializers won't differ in the field initialization. - - if (!isConstructor) - { - // rude edit: Editing a field/property initializer of a partial type. - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.PartialTypeInitializerUpdate, - newDeclaration.Span, - newDeclaration, - new[] { GetDisplayName(newDeclaration, EditKind.Update) })); - return false; - } - - // TODO (bug https://github.com/dotnet/roslyn/issues/2504) - if (editKind == SemanticEditKind.Insert) - { - if (HasMemberInitializerContainingLambda(oldType, newSymbol.IsStatic, cancellationToken)) - { - // rude edit: Adding a constructor to a type with a field or property initializer that contains an anonymous function - diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, GetDiagnosticSpan(newDeclaration, EditKind.Insert))); - return false; - } - - syntaxMap = null; - } - else - { - syntaxMap = CreateSyntaxMapForPartialTypeConstructor(oldType, newType, newModel, syntaxMap); - } - - return false; - } - Dictionary constructorEdits; - if (newSymbol.IsStatic) + if (isStatic) { - constructorEdits = staticConstructorEdits ??= new(); + constructorEdits = staticConstructorEdits ??= PooledDictionary.GetInstance(); } else { - constructorEdits = instanceConstructorEdits ??= new(); + constructorEdits = instanceConstructorEdits ??= PooledDictionary.GetInstance(); } if (!constructorEdits.TryGetValue(newType, out var constructorEdit)) @@ -2846,48 +2946,69 @@ private bool DeferConstructorEdit( constructorEdits.Add(newType, constructorEdit = new ConstructorEdit(oldType)); } - constructorEdit.ChangedDeclarations.Add(newDeclaration, syntaxMap); - - return true; + if (newDeclaration != null) + { + constructorEdit.ChangedDeclarations.Add(newDeclaration, syntaxMap); + } } private void AddConstructorEdits( - Dictionary updatedTypes, + IReadOnlyDictionary updatedTypes, Match topMatch, - SemanticModel oldModel, - HashSet newSymbolsWithEdit, + SemanticModel? oldModel, + Compilation oldCompilation, + IReadOnlySet processedSymbols, bool isStatic, - [Out] ArrayBuilder semanticEdits, + [Out] ArrayBuilder semanticEdits, [Out] ArrayBuilder diagnostics, CancellationToken cancellationToken) { - foreach (var (newType, update) in updatedTypes) + var oldSyntaxTree = topMatch.OldRoot.SyntaxTree; + var newSyntaxTree = topMatch.NewRoot.SyntaxTree; + + foreach (var (newType, updatesInCurrentDocument) in updatedTypes) { - var oldType = update.OldType; + var oldType = updatesInCurrentDocument.OldType; + + var anyInitializerUpdatesInCurrentDocument = updatesInCurrentDocument.ChangedDeclarations.Keys.Any(IsDeclarationWithInitializer); - Debug.Assert(!IsPartial(oldType)); - Debug.Assert(!IsPartial(newType)); + // If any of the partial declarations of the new or the old type are in another document + // the edit will need to be merged with other partial edits with matching partial type + static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) + => reference.SyntaxTree != syntaxTree; - var anyInitializerUpdates = update.ChangedDeclarations.Keys.Any(IsDeclarationWithInitializer); + var isPartialEdit = + oldType.DeclaringSyntaxReferences.Any(IsNotInDocument, oldSyntaxTree) || + newType.DeclaringSyntaxReferences.Any(IsNotInDocument, newSyntaxTree); + + // Create a syntax map that aggregates syntax maps of the constructor body and all initializers in this document. + // Use syntax maps stored in update.ChangedDeclarations and fallback to 1:1 map for unchanged members. + // + // This aggregated map will be combined with similar maps capturing members declared in partial type declarations + // located in other documents when the semantic edits are merged across all changed documents of the project. + // + // We will create an aggregate syntax map even in cases when we don't necessarily need it, + // for example if none of the edited declarations are active. It's ok to have a map that we don't need. + // This is simpler than detecting whether or not some of the initializers/constructors contain active statements. + var aggregateSyntaxMap = CreateAggregateSyntaxMap(topMatch.ReverseMatches, updatesInCurrentDocument.ChangedDeclarations); bool? lazyOldTypeHasMemberInitializerContainingLambda = null; foreach (var newCtor in isStatic ? newType.StaticConstructors : newType.InstanceConstructors) { - if (newSymbolsWithEdit.Contains(newCtor)) + if (processedSymbols.Contains(newCtor)) { // we already have an edit for the new constructor continue; } + var newCtorKey = SymbolKey.Create(newCtor, cancellationToken); + ISymbol? oldCtor; if (!newCtor.IsImplicitlyDeclared) { // Constructors have to have a single declaration syntax, they can't be partial - var newDeclaration = FindMemberDeclaration(root: null, GetSymbolSyntax(newCtor, cancellationToken)); - - // Partial types were filtered out previously and rude edits reported. - RoslynDebug.Assert(newDeclaration?.SyntaxTree == topMatch.NewRoot.SyntaxTree); + var newDeclaration = GetSymbolDeclarationSyntax(newCtor.DeclaringSyntaxReferences.Single(), cancellationToken); // Constructor that doesn't contain initializers had a corresponding semantic edit produced previously // or was not edited. In either case we should not produce a semantic edit for it. @@ -2898,62 +3019,87 @@ private void AddConstructorEdits( // If no initializer updates were made in the type we only need to produce semantic edits for constructors // whose body has been updated, otherwise we need to produce edits for all constructors that include initializers. - if (!anyInitializerUpdates && !update.ChangedDeclarations.ContainsKey(newDeclaration)) + // If changes were made to initializers or constructors of a partial type in another document they will be merged + // when aggregating semantic edits from all changed documents. Rude edits resulting from those changes, if any, will + // be reported in the document they were made in. + if (!anyInitializerUpdatesInCurrentDocument && !updatesInCurrentDocument.ChangedDeclarations.ContainsKey(newDeclaration)) { continue; } + // To avoid costly SymbolKey resolution we first try to match the constructor in the current document + // and special case parameter-less constructor. if (topMatch.TryGetOldNode(newDeclaration, out var oldDeclaration)) { - // If the constructor wasn't explicitly edited and its body edit is disallowed report an error. - var diagnosticCount = diagnostics.Count; - ReportMemberUpdateRudeEdits(diagnostics, newDeclaration, span: null); - if (diagnostics.Count > diagnosticCount) - { - continue; - } - + Contract.ThrowIfNull(oldModel); oldCtor = oldModel.GetDeclaredSymbol(oldDeclaration, cancellationToken); - Debug.Assert(oldCtor != null); + Contract.ThrowIfNull(oldCtor); } else if (newCtor.Parameters.Length == 0) { oldCtor = TryGetParameterlessConstructor(oldType, isStatic); } else + { + var resolution = newCtorKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken); + + // There may be semantic errors in the compilation that result in multiple candidates. + // Pick the first candidate. + + oldCtor = resolution.Symbol; + } + + if (oldCtor == null && HasMemberInitializerContainingLambda(oldType, isStatic, ref lazyOldTypeHasMemberInitializerContainingLambda, cancellationToken)) { // TODO (bug https://github.com/dotnet/roslyn/issues/2504) - if (HasMemberInitializerContainingLambda(oldType, isStatic, ref lazyOldTypeHasMemberInitializerContainingLambda, cancellationToken)) - { - // rude edit: Adding a constructor to a type with a field or property initializer that contains an anonymous function - diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, GetDiagnosticSpan(newDeclaration, EditKind.Insert))); - continue; - } + // rude edit: Adding a constructor to a type with a field or property initializer that contains an anonymous function + diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, GetDiagnosticSpan(newDeclaration, EditKind.Insert))); + continue; + } + + // Report an error if the updated constructor's declaration is in the current document + // and its body edit is disallowed (e.g. contains stackalloc). + if (oldCtor != null && newDeclaration?.SyntaxTree == newSyntaxTree && anyInitializerUpdatesInCurrentDocument) + { + // attribute rude edit to one of the modified members + var firstSpan = updatesInCurrentDocument.ChangedDeclarations.Keys.Where(IsDeclarationWithInitializer).Aggregate( + (min: int.MaxValue, span: default(TextSpan)), + (accumulate, node) => (node.SpanStart < accumulate.min) ? (node.SpanStart, node.Span) : accumulate).span; - // no initializer contains lambdas => we don't need a syntax map - oldCtor = null; + Contract.ThrowIfTrue(firstSpan.IsEmpty); + ReportMemberUpdateRudeEdits(diagnostics, newDeclaration, firstSpan); } } else { + // New constructor is implicitly declared so it must be parameterless. + // + // Instance constructor: + // Its presence indicates there are no other instance constructors in the new type and therefore + // there must be a single parameterless instance constructor in the old type (constructors with parameters can't be removed). + // + // Static constructor: + // Static constructor is always parameterless and not implicitly generated if there are no static initializers. oldCtor = TryGetParameterlessConstructor(oldType, isStatic); + Contract.ThrowIfFalse(isStatic || oldCtor != null); } - // We assume here that the type is not partial and thus we collected all changed - // field and property initializers in update.ChangedDeclarations and we only need - // the current top match to map nodes from all unchanged initializers. - // - // We will create an aggregate syntax map even in cases when we don't necessarily need it, - // for example if none of the edited declarations are active. It's ok to have a map that we don't need. - var aggregateSyntaxMap = (oldCtor != null && update.ChangedDeclarations.Count > 0) ? - CreateAggregateSyntaxMap(topMatch.ReverseMatches, update.ChangedDeclarations) : null; - - semanticEdits.Add(new SemanticEdit( - (oldCtor == null) ? SemanticEditKind.Insert : SemanticEditKind.Update, - oldCtor, - newCtor, - aggregateSyntaxMap, - preserveLocalVariables: aggregateSyntaxMap != null)); + if (oldCtor != null) + { + semanticEdits.Add(new SemanticEditInfo( + SemanticEditKind.Update, + newCtorKey, + aggregateSyntaxMap, + partialType: isPartialEdit ? SymbolKey.Create(newType, cancellationToken) : null)); + } + else + { + semanticEdits.Add(new SemanticEditInfo( + SemanticEditKind.Insert, + newCtorKey, + syntaxMap: null, + partialType: null)); + } } } } @@ -2979,7 +3125,7 @@ private bool HasMemberInitializerContainingLambda(INamedTypeSymbol type, bool is (member.Kind == SymbolKind.Field || member.Kind == SymbolKind.Property) && member.DeclaringSyntaxReferences.Length > 0) // skip generated fields (e.g. VB auto-property backing fields) { - var syntax = GetSymbolSyntax(member, cancellationToken); + var syntax = GetSymbolDeclarationSyntax(member.DeclaringSyntaxReferences.Single(), cancellationToken); if (IsDeclarationWithInitializer(syntax) && ContainsLambda(syntax)) { return true; @@ -3016,28 +3162,30 @@ private void ReportLambdaAndClosureRudeEdits( IReadOnlyDictionary? matchedLambdas, BidirectionalMap map, ArrayBuilder diagnostics, - out bool newBodyHasLambdas, + out bool syntaxMapRequired, CancellationToken cancellationToken) { + syntaxMapRequired = false; + if (matchedLambdas != null) { var anySignatureErrors = false; foreach (var (oldLambdaBody, newLambdaInfo) in matchedLambdas) { - // The map now contains only matched lambdas. Any unmatched ones would have contained an active statement and - // a rude edit would be reported in syntax analysis phase. - RoslynDebug.Assert(newLambdaInfo.Match != null && newLambdaInfo.NewBody != null); + // Any unmatched lambdas would have contained an active statement and a rude edit would be reported in syntax analysis phase. + // Skip the rest of lambda and closure analysis if such lambdas are present. + if (newLambdaInfo.Match == null || newLambdaInfo.NewBody == null) + { + return; + } ReportLambdaSignatureRudeEdits(oldModel, oldLambdaBody, newModel, newLambdaInfo.NewBody, diagnostics, out var hasErrors, cancellationToken); anySignatureErrors |= hasErrors; } ArrayBuilder? lazyNewErroneousClauses = null; - foreach (var entry in map.Forward) + foreach (var (oldQueryClause, newQueryClause) in map.Forward) { - var oldQueryClause = entry.Key; - var newQueryClause = entry.Value; - if (!QueryClauseLambdasTypeEquivalent(oldModel, oldQueryClause, newModel, newQueryClause, cancellationToken)) { lazyNewErroneousClauses ??= ArrayBuilder.GetInstance(); @@ -3066,7 +3214,6 @@ select clausesByQuery.First()) // only dig into captures if lambda signatures match if (anySignatureErrors) { - newBodyHasLambdas = true; return; } } @@ -3100,7 +3247,6 @@ select clausesByQuery.First()) if (anyCaptureErrors) { - newBodyHasLambdas = true; return; } @@ -3172,7 +3318,6 @@ select clausesByQuery.First()) if (mappedLambdasHaveErrors) { - newBodyHasLambdas = true; return; } } @@ -3200,8 +3345,6 @@ select clausesByQuery.First()) // We currently allow #1, #2, and #3 and report a rude edit for the other cases. // In future we might be able to enable more. - newBodyHasLambdas = false; - var containingTypeDeclaration = TryGetContainingTypeDeclaration(newMemberBody); var isInInterfaceDeclaration = containingTypeDeclaration != null && IsInterfaceDeclaration(containingTypeDeclaration); @@ -3227,7 +3370,7 @@ select clausesByQuery.First()) } } - newBodyHasLambdas = true; + syntaxMapRequired = true; } } @@ -3350,8 +3493,10 @@ private static void BuildIndex(Dictionary index, ImmutableArray } } - protected static SyntaxNode GetSymbolSyntax(ISymbol local, CancellationToken cancellationToken) - => local.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); + /// + /// Returns node that represents a declaration of the symbol whose is passed in. + /// + protected abstract SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference reference, CancellationToken cancellationToken); private static TextSpan GetThisParameterDiagnosticSpan(ISymbol member) => member.Locations.First().SourceSpan; @@ -3370,7 +3515,7 @@ private static (SyntaxNode? Node, int Ordinal) GetParameterKey(IParameterSymbol if (containingLambda?.MethodKind == MethodKind.LambdaMethod || containingLambda?.MethodKind == MethodKind.LocalFunction) { - var oldContainingLambdaSyntax = GetSymbolSyntax(containingLambda, cancellationToken); + var oldContainingLambdaSyntax = containingLambda.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); return (oldContainingLambdaSyntax, parameter.Ordinal); } else @@ -3460,7 +3605,7 @@ private void CalculateCapturedVariablesMaps( } else { - oldLocalCapturesBySyntax.Add(GetSymbolSyntax(oldCapture, cancellationToken), i); + oldLocalCapturesBySyntax.Add(oldCapture.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken), i); } } @@ -3493,7 +3638,7 @@ private void CalculateCapturedVariablesMaps( } else { - var newCaptureSyntax = GetSymbolSyntax(newCapture, cancellationToken); + var newCaptureSyntax = newCapture.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); // variable doesn't exists in the old method or has not been captured prior the edit: if (!map.Reverse.TryGetValue(newCaptureSyntax, out var mappedOldSyntax) || @@ -3737,7 +3882,7 @@ private SyntaxNode GetCapturedVariableScope(ISymbol localOrParameter, SyntaxNode // lambda parameters and C# constructor parameters are lifted to their own scope: if ((member as IMethodSymbol)?.MethodKind == MethodKind.AnonymousFunction || HasParameterClosureScope(member)) { - var result = GetSymbolSyntax(localOrParameter, cancellationToken); + var result = localOrParameter.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); Debug.Assert(IsLambda(result)); return result; } @@ -3745,7 +3890,7 @@ private SyntaxNode GetCapturedVariableScope(ISymbol localOrParameter, SyntaxNode return memberBody; } - var node = GetSymbolSyntax(localOrParameter, cancellationToken); + var node = localOrParameter.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); while (true) { RoslynDebug.Assert(node is object); @@ -3774,15 +3919,10 @@ private static bool AreEquivalentClosureScopes(SyntaxNode oldScopeOpt, SyntaxNod private void ReportStateMachineRudeEdits( Compilation oldCompilation, - UpdatedMemberInfo updatedInfo, ISymbol oldMember, + SyntaxNode newBody, ArrayBuilder diagnostics) { - if (!updatedInfo.OldHasStateMachineSuspensionPoint) - { - return; - } - // Only methods, local functions and anonymous functions can be async/iterators machines, // but don't assume so to be resiliant against errors in code. if (oldMember is not IMethodSymbol oldMethod) @@ -3808,8 +3948,8 @@ private void ReportStateMachineRudeEdits( { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.UpdatingStateMachineMethodMissingAttribute, - GetBodyDiagnosticSpan(updatedInfo.NewBody, EditKind.Update), - updatedInfo.NewBody, + GetBodyDiagnosticSpan(newBody, EditKind.Update), + newBody, new[] { stateMachineAttributeQualifiedName })); } } @@ -3837,6 +3977,27 @@ private static bool TryGetTextSpan(TextLineCollection lines, LinePositionSpan li return true; } + internal static void AddNodes(ArrayBuilder nodes, SyntaxList list) + where T : SyntaxNode + { + foreach (var node in list) + { + nodes.Add(node); + } + } + + internal static void AddNodes(ArrayBuilder nodes, SeparatedSyntaxList? list) + where T : SyntaxNode + { + if (list.HasValue) + { + foreach (var node in list.Value) + { + nodes.Add(node); + } + } + } + #endregion #region Testing @@ -3851,22 +4012,7 @@ internal readonly struct TestAccessor public TestAccessor(AbstractEditAndContinueAnalyzer abstractEditAndContinueAnalyzer) => _abstractEditAndContinueAnalyzer = abstractEditAndContinueAnalyzer; - internal void AnalyzeMemberBodiesSyntax( - EditScript script, - Dictionary editMap, - SourceText oldText, - SourceText newText, - ImmutableArray oldActiveStatements, - ImmutableArray newActiveStatementSpans, - [Out] ImmutableArray.Builder newActiveStatements, - [Out] ImmutableArray>.Builder newExceptionRegions, - [Out] ArrayBuilder updatedMethods, - [Out] ArrayBuilder diagnostics) - { - _abstractEditAndContinueAnalyzer.AnalyzeMemberBodiesSyntax(script, editMap, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions, updatedMethods, diagnostics); - } - - internal void ReportTopLevelSynctactiveRudeEdits(ArrayBuilder diagnostics, EditScript syntacticEdits, Dictionary editMap) + internal void ReportTopLevelSyntacticRudeEdits(ArrayBuilder diagnostics, EditScript syntacticEdits, Dictionary editMap) => _abstractEditAndContinueAnalyzer.ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap); internal void AnalyzeUnchangedDocument( @@ -3911,22 +4057,6 @@ internal void AnalyzeTrivia( { _abstractEditAndContinueAnalyzer.AnalyzeTrivia(oldSource, newSource, topMatch, editMap, triviaEdits, lineEdits, diagnostics, cancellationToken); } - - internal void AnalyzeSemantics( - EditScript editScript, - Dictionary editMap, - SourceText oldText, - ImmutableArray oldActiveStatements, - ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, - ArrayBuilder updatedMembers, - SemanticModel oldModel, - SemanticModel newModel, - [Out] ArrayBuilder semanticEdits, - [Out] ArrayBuilder diagnostics, - CancellationToken cancellationToken) - { - _abstractEditAndContinueAnalyzer.AnalyzeSemantics(editScript, editMap, oldText, oldActiveStatements, triviaEdits, updatedMembers, oldModel, newModel, semanticEdits, diagnostics, cancellationToken); - } } #endregion diff --git a/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs b/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs index 88bb455a730ff..61c2cf775c9ef 100644 --- a/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs +++ b/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs @@ -36,7 +36,7 @@ internal sealed class DocumentAnalysisResults /// /// Edits made in the document, or null if the document is unchanged, has syntax errors or rude edits. /// - public ImmutableArray SemanticEdits { get; } + public ImmutableArray SemanticEdits { get; } /// /// Exception regions -- spans of catch and finally handlers that surround the active statements. @@ -83,7 +83,7 @@ public DocumentAnalysisResults( DocumentId documentId, ImmutableArray activeStatementsOpt, ImmutableArray rudeEdits, - ImmutableArray semanticEditsOpt, + ImmutableArray semanticEditsOpt, ImmutableArray> exceptionRegionsOpt, ImmutableArray lineEditsOpt, bool hasChanges, @@ -100,6 +100,8 @@ public DocumentAnalysisResults( } else if (hasChanges) { + Debug.Assert(!activeStatementsOpt.IsDefault); + if (rudeEdits.Length > 0) { Debug.Assert(semanticEditsOpt.IsDefault); @@ -108,7 +110,6 @@ public DocumentAnalysisResults( } else { - Debug.Assert(!activeStatementsOpt.IsDefault); Debug.Assert(!semanticEditsOpt.IsDefault); Debug.Assert(!exceptionRegionsOpt.IsDefault); Debug.Assert(!lineEditsOpt.IsDefault); @@ -145,34 +146,29 @@ public bool HasChangesAndSyntaxErrors public bool HasSignificantValidChanges => HasChanges && (!SemanticEdits.IsDefaultOrEmpty || !LineEdits.IsDefaultOrEmpty); - public static DocumentAnalysisResults SyntaxErrors(DocumentId documentId, bool hasChanges) + /// + /// Report errors blocking the document analysis. + /// + public static DocumentAnalysisResults SyntaxErrors(DocumentId documentId, ImmutableArray rudeEdits, bool hasChanges) => new( documentId, activeStatementsOpt: default, - rudeEdits: ImmutableArray.Empty, + rudeEdits: rudeEdits, semanticEditsOpt: default, exceptionRegionsOpt: default, lineEditsOpt: default, hasChanges, hasSyntaxErrors: true); - public static DocumentAnalysisResults Errors(DocumentId documentId, ImmutableArray activeStatementsOpt, ImmutableArray rudeEdits) - => new( - documentId, - activeStatementsOpt, - rudeEdits, - semanticEditsOpt: default, - exceptionRegionsOpt: default, - lineEditsOpt: default, - hasChanges: true, - hasSyntaxErrors: false); - + /// + /// Report unchanged document results. + /// public static DocumentAnalysisResults Unchanged(DocumentId documentId, ImmutableArray activeStatements, ImmutableArray> exceptionRegions) => new( documentId, activeStatements, rudeEdits: ImmutableArray.Empty, - semanticEditsOpt: ImmutableArray.Empty, + semanticEditsOpt: ImmutableArray.Empty, exceptionRegions, lineEditsOpt: ImmutableArray.Empty, hasChanges: false, diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index dd0b371fbfe99..bb0ccbebb5e0d 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -66,7 +66,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.InsertAroundActiveStatement, nameof(FeaturesResources.Adding_0_around_an_active_statement_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.DeleteAroundActiveStatement, nameof(FeaturesResources.Deleting_0_around_an_active_statement_will_prevent_the_debug_session_from_continuing)); - AddRudeEdit(RudeEditKind.DeleteActiveStatement, nameof(FeaturesResources.An_active_statement_has_been_removed_from_its_original_method_You_must_revert_your_changes_to_continue_or_restart_the_debugging_session)); + AddRudeEdit(RudeEditKind.DeleteActiveStatement, nameof(FeaturesResources.Removing_0_that_contains_an_active_statement_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.UpdateAroundActiveStatement, nameof(FeaturesResources.Updating_a_0_around_an_active_statement_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.UpdateExceptionHandlerOfActiveTry, nameof(FeaturesResources.Modifying_a_catch_finally_handler_with_an_active_statement_in_the_try_block_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.UpdateTryOrCatchWithActiveFinally, nameof(FeaturesResources.Modifying_a_try_catch_finally_statement_when_the_finally_block_is_active_will_prevent_the_debug_session_from_continuing)); @@ -104,13 +104,12 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.GenericTypeUpdate, nameof(FeaturesResources.Modifying_a_method_inside_the_context_of_a_generic_type_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.GenericTypeTriviaUpdate, nameof(FeaturesResources.Modifying_whitespace_or_comments_in_0_inside_the_context_of_a_generic_type_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.GenericTypeInitializerUpdate, nameof(FeaturesResources.Modifying_the_initializer_of_0_in_a_generic_type_will_prevent_the_debug_session_from_continuing)); - AddRudeEdit(RudeEditKind.PartialTypeInitializerUpdate, nameof(FeaturesResources.Modifying_the_initializer_of_0_in_a_partial_type_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, nameof(FeaturesResources.Adding_a_constructor_to_a_type_with_a_field_or_property_initializer_that_contains_an_anonymous_function_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.RenamingCapturedVariable, nameof(FeaturesResources.Renaming_a_captured_variable_from_0_to_1_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.StackAllocUpdate, nameof(FeaturesResources.Modifying_0_which_contains_the_stackalloc_operator_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ExperimentalFeaturesEnabled, nameof(FeaturesResources.Modifying_source_with_experimental_language_features_enabled_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.AwaitStatementUpdate, nameof(FeaturesResources.Updating_a_complex_statement_containing_an_await_expression_will_prevent_the_debug_session_from_continuing)); - AddRudeEdit(RudeEditKind.ChangingConstructorVisibility, nameof(FeaturesResources.Changing_visibility_of_a_constructor_will_prevent_the_debug_session_from_continuing)); + AddRudeEdit(RudeEditKind.ChangingVisibility, nameof(FeaturesResources.Changing_visibility_of_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.CapturingVariable, nameof(FeaturesResources.Capturing_variable_0_that_hasn_t_been_captured_before_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.NotCapturingVariable, nameof(FeaturesResources.Ceasing_to_capture_variable_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.DeletingCapturedVariable, nameof(FeaturesResources.Deleting_captured_variable_0_will_prevent_the_debug_session_from_continuing)); @@ -127,7 +126,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.ActiveStatementLambdaRemoved, nameof(FeaturesResources.Removing_0_that_contains_an_active_statement_will_prevent_the_debug_session_from_continuing)); // TODO: change the error message to better explain what's going on AddRudeEdit(RudeEditKind.PartiallyExecutedActiveStatementUpdate, nameof(FeaturesResources.Updating_an_active_statement_will_prevent_the_debug_session_from_continuing)); - AddRudeEdit(RudeEditKind.PartiallyExecutedActiveStatementDelete, nameof(FeaturesResources.An_active_statement_has_been_removed_from_its_original_method_You_must_revert_your_changes_to_continue_or_restart_the_debugging_session)); + AddRudeEdit(RudeEditKind.PartiallyExecutedActiveStatementDelete, nameof(FeaturesResources.Removing_0_that_contains_an_active_statement_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.InsertFile, nameof(FeaturesResources.Adding_a_new_file_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, nameof(FeaturesResources.Updating_async_or_iterator_modifier_around_an_active_statement_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, nameof(FeaturesResources.Attribute_0_is_missing_Updating_an_async_method_or_an_iterator_will_prevent_the_debug_session_from_continuing)); @@ -144,6 +143,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.MemberBodyInternalError, nameof(FeaturesResources.Modifying_body_of_member_will_prevent_the_debug_session_from_continuing_due_to_internal_error)); AddRudeEdit(RudeEditKind.MemberBodyTooBig, nameof(FeaturesResources.Modifying_body_of_member_will_prevent_the_debug_session_from_continuing_because_the_body_has_too_many_statements)); AddRudeEdit(RudeEditKind.SourceFileTooBig, nameof(FeaturesResources.Modifying_source_file_will_prevent_the_debug_session_from_continuing_because_the_file_is_too_big)); + AddRudeEdit(RudeEditKind.InsertIntoGenericType, nameof(FeaturesResources.Adding_0_into_a_generic_type_will_prevent_the_debug_session_from_continuing)); // VB specific AddRudeEdit(RudeEditKind.HandlesClauseUpdate, nameof(FeaturesResources.Updating_the_Handles_clause_of_0_will_prevent_the_debug_session_from_continuing)); diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index 0ffcef9df0721..411cce2984d1c 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Roslyn.Utilities; @@ -516,7 +517,7 @@ private static ProjectAnalysisSummary GetProjectAnalysisSymmary(ImmutableArray changedDocumentAnalyses) + private static ProjectChanges GetProjectChanges(Compilation newCompilation, ImmutableArray changedDocumentAnalyses, CancellationToken cancellationToken) { try { - using var _1 = ArrayBuilder.GetInstance(out var allEdits); + using var _1 = ArrayBuilder.GetInstance(out var allEdits); using var _2 = ArrayBuilder<(DocumentId, ImmutableArray)>.GetInstance(out var allLineEdits); using var _3 = ArrayBuilder<(DocumentId, ImmutableArray, ImmutableArray>)>.GetInstance(out var activeStatementsInChangedDocuments); - using var _4 = ArrayBuilder.GetInstance(out var allAddedSymbols); foreach (var analysis in changedDocumentAnalyses) { @@ -567,14 +567,6 @@ private static ProjectChanges GetProjectChanges(ImmutableArray 0) @@ -588,10 +580,12 @@ private static ProjectChanges GetProjectChanges(ImmutableArray edits, + out ImmutableArray mergedEdits, + out ImmutableHashSet addedSymbols, + CancellationToken cancellationToken) + { + using var _0 = ArrayBuilder.GetInstance(edits.Count, out var mergedEditsBuilder); + using var _1 = PooledHashSet.GetInstance(out var addedSymbolsBuilder); + using var _2 = ArrayBuilder.GetInstance(edits.Count, out var resolvedSymbols); + + foreach (var edit in edits) + { + var resolution = edit.Symbol.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken); + Contract.ThrowIfNull(resolution.Symbol); + resolvedSymbols.Add(resolution.Symbol); + } + + for (var i = 0; i < edits.Count; i++) + { + var edit = edits[i]; + + if (edit.PartialType == null) + { + var symbol = resolvedSymbols[i]; + + if (edit.Kind == SemanticEditKind.Insert) + { + // Inserts do not need partial type merging. + addedSymbolsBuilder.Add(symbol); + } + + mergedEditsBuilder.Add(new SemanticEdit( + edit.Kind, + oldSymbol: null, + newSymbol: symbol, + syntaxMap: edit.SyntaxMap, + preserveLocalVariables: edit.SyntaxMap != null)); + } + } + + // no partial type merging needed: + if (edits.Count == mergedEditsBuilder.Count) + { + mergedEdits = mergedEditsBuilder.ToImmutable(); + addedSymbols = addedSymbolsBuilder.ToImmutableHashSet(); + return; + } + + // Calculate merged syntax map for each partial type symbol: + + var symbolKeyComparer = SymbolKey.GetComparer(ignoreCase: false, ignoreAssemblyKeys: true); + var mergedSyntaxMaps = new Dictionary>(symbolKeyComparer); + + foreach (var partialTypeEdits in edits.Where(edit => edit.PartialType != null).GroupBy(edit => edit.PartialType!.Value, symbolKeyComparer)) + { + // the symbols may only be constructors, all of which have a single syntax declaration + Debug.Assert(partialTypeEdits.Select((edit, index) => resolvedSymbols[index]).All(symbol => symbol is IMethodSymbol + { + MethodKind: MethodKind.Constructor or MethodKind.StaticConstructor or MethodKind.SharedConstructor, + DeclaringSyntaxReferences: { Length: 1 } + })); + + Debug.Assert(partialTypeEdits.All(edit => edit.SyntaxMap != null)); + + var trees = partialTypeEdits.Select((edit, index) => resolvedSymbols[index].DeclaringSyntaxReferences.Single().SyntaxTree).ToImmutableArray(); + var syntaxMaps = partialTypeEdits.SelectAsArray(edit => edit.SyntaxMap!); + + mergedSyntaxMaps.Add(partialTypeEdits.Key, node => syntaxMaps[trees.IndexOf(node.SyntaxTree)](node)); + } + + // Deduplicate updates based on new symbol and use merged syntax map calculated above for a given partial type. + + using var _3 = PooledHashSet.GetInstance(out var visitedSymbols); + + for (var i = 0; i < edits.Count; i++) + { + var edit = edits[i]; + + if (edit.PartialType != null) + { + Contract.ThrowIfFalse(edit.Kind == SemanticEditKind.Update); + + var newSymbol = resolvedSymbols[i]; + if (visitedSymbols.Add(newSymbol)) + { + mergedEditsBuilder.Add(new SemanticEdit(SemanticEditKind.Update, oldSymbol: null, newSymbol, mergedSyntaxMaps[edit.PartialType.Value], preserveLocalVariables: true)); + } + } + } + + mergedEdits = mergedEditsBuilder.ToImmutable(); + addedSymbols = addedSymbolsBuilder.ToImmutableHashSet(); + } + public async Task EmitSolutionUpdateAsync(Solution solution, SolutionActiveStatementSpanProvider solutionActiveStatementSpanProvider, CancellationToken cancellationToken) { try @@ -711,7 +800,7 @@ public async Task EmitSolutionUpdateAsync(Solution solution, Sol var currentCompilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(currentCompilation); - var projectChanges = GetProjectChanges(changedDocumentAnalyses); + var projectChanges = GetProjectChanges(currentCompilation, changedDocumentAnalyses, cancellationToken); var baseActiveStatements = await BaseActiveStatements.GetValueAsync(cancellationToken).ConfigureAwait(false); // Exception regions of active statements in changed documents are calculated (non-default), diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs index 065ad8bcfc229..c8a5d18c294ad 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs @@ -54,7 +54,7 @@ internal enum RudeEditKind : ushort GenericTypeUpdate = 38, GenericTypeTriviaUpdate = 39, GenericTypeInitializerUpdate = 40, - PartialTypeInitializerUpdate = 41, + // PartialTypeInitializerUpdate = 41, // AsyncMethodUpdate = 42, // AsyncMethodTriviaUpdate = 43, StackAllocUpdate = 44, @@ -62,7 +62,7 @@ internal enum RudeEditKind : ushort ExperimentalFeaturesEnabled = 45, AwaitStatementUpdate = 46, - ChangingConstructorVisibility = 47, + ChangingVisibility = 47, CapturingVariable = 48, NotCapturingVariable = 49, @@ -116,5 +116,6 @@ internal enum RudeEditKind : ushort MemberBodyInternalError = 88, SourceFileTooBig = 89, MemberBodyTooBig = 90, + InsertIntoGenericType = 91, } } diff --git a/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs b/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs new file mode 100644 index 0000000000000..2663acf1067ce --- /dev/null +++ b/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs @@ -0,0 +1,49 @@ +// 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.Emit; + +namespace Microsoft.CodeAnalysis.EditAndContinue +{ + internal readonly struct SemanticEditInfo + { + /// + /// or . + /// + public SemanticEditKind Kind { get; } + + /// + /// If is represents the inserted symbol in the new compilation. + /// If is represents the updated symbol in both compilations. + /// + /// We use to represent the symbol rather then , + /// since different semantic edits might hav ebeen calculated against different solution snapshot and thus symbols are not directly comparable. + /// When the edits are processed we map the to the current compilation. + /// + public SymbolKey Symbol { get; } + + public Func? SyntaxMap { get; } + + /// + /// Specified if the edit needs to be merged with other edits of the same . + /// for edits of non-partial types or their members and of a member of a partial type that do not require merging. + /// + /// If specified, the is incomplete: it only provides mapping of the changed members of a single partial type declaration. + /// + public SymbolKey? PartialType { get; } + + public SemanticEditInfo( + SemanticEditKind kind, + SymbolKey symbol, + Func? syntaxMap, + SymbolKey? partialType) + { + Kind = kind; + Symbol = symbol; + SyntaxMap = syntaxMap; + PartialType = partialType; + } + } +} diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 3310bdbcd4df1..755299062dae6 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -363,8 +363,8 @@ Updating a complex statement containing an await expression will prevent the debug session from continuing. - - Changing visibility of a constructor will prevent the debug session from continuing. + + Changing visibility of {0} will prevent the debug session from continuing. Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. @@ -408,6 +408,9 @@ Adding '{0}' into an interface will prevent the debug session from continuing. + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into an interface method will prevent the debug session from continuing. @@ -496,7 +499,7 @@ Moving '{0}' will prevent the debug session from continuing. - Deleting '{0}' will prevent the debug session from continuing. + Deleting {0} will prevent the debug session from continuing. Deleting '{0}' around an active statement will prevent the debug session from continuing. @@ -535,9 +538,6 @@ Modifying the initializer of '{0}' in a generic type will prevent the debug session from continuing. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. @@ -572,7 +572,7 @@ Updating an active statement will prevent the debug session from continuing. - Removing '{0}' that contains an active statement will prevent the debug session from continuing. + Removing {0} that contains an active statement will prevent the debug session from continuing. Adding a new file will prevent the debug session from continuing. @@ -791,6 +791,9 @@ Do you want to continue? constructor + + static constructor + auto-property @@ -2805,6 +2808,13 @@ Zero-width positive lookbehind assertions are typically used at the beginning of {0} - {1} + + {0} '{1}' + e.g. "method 'M'" + + + code + Convert to record diff --git a/src/Features/Core/Portable/InlineTemporary/AbstractInlineTemporaryCodeRefactoringProvider.cs b/src/Features/Core/Portable/InlineTemporary/AbstractInlineTemporaryCodeRefactoringProvider.cs index 251a6111c6ee7..acff36fb962da 100644 --- a/src/Features/Core/Portable/InlineTemporary/AbstractInlineTemporaryCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/InlineTemporary/AbstractInlineTemporaryCodeRefactoringProvider.cs @@ -30,7 +30,7 @@ protected static async Task> GetReferenceLocat // Do not cascade when finding references to this local. Cascading can cause us to find linked // references as well which can throw things off for us. For inline variable, we only care about the // direct real references in this project context. - var options = FindReferencesSearchOptions.Default.WithCascade(cascade: false); + var options = FindReferencesSearchOptions.Default.With(cascade: false); var findReferencesResult = await SymbolFinder.FindReferencesAsync( local, document.Project.Solution, options, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs index 6bf4c60333fdb..d0e250595a5fc 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs @@ -33,6 +33,8 @@ public WrappedMethodSymbol(IMethodSymbol methodSymbol, bool canImplementImplicit public bool IsReadOnly => _symbol.IsReadOnly; public bool IsInitOnly => _symbol.IsInitOnly; + public System.Reflection.MethodImplAttributes MethodImplementationFlags => _symbol.MethodImplementationFlags; + public ImmutableArray ExplicitInterfaceImplementations { get diff --git a/src/Features/Core/Portable/SolutionCrawler/State/AbstractAnalyzerState.cs b/src/Features/Core/Portable/SolutionCrawler/State/AbstractAnalyzerState.cs index 119913a91aa92..7d3043672cb77 100644 --- a/src/Features/Core/Portable/SolutionCrawler/State/AbstractAnalyzerState.cs +++ b/src/Features/Core/Portable/SolutionCrawler/State/AbstractAnalyzerState.cs @@ -61,7 +61,8 @@ public async Task TryGetExistingDataAsync(TValue value, CancellationToken try { - using var storage = await persistService.GetStorageAsync(solution, cancellationToken).ConfigureAwait(false); + var storage = await persistService.GetStorageAsync(solution, cancellationToken).ConfigureAwait(false); + await using var _ = storage.ConfigureAwait(false); using var stream = await ReadStreamAsync(storage, value, cancellationToken).ConfigureAwait(false); if (stream != null) @@ -102,7 +103,8 @@ private async Task WriteToStreamAsync(TValue value, TData data, Cancellati var solution = GetSolution(value); var persistService = solution.Workspace.Services.GetService(); - using var storage = await persistService.GetStorageAsync(solution, cancellationToken).ConfigureAwait(false); + var storage = await persistService.GetStorageAsync(solution, cancellationToken).ConfigureAwait(false); + await using var _ = storage.ConfigureAwait(false); stream.Position = 0; return await WriteStreamAsync(storage, value, stream, cancellationToken).ConfigureAwait(false); } diff --git a/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs index 714219d32c531..68a960905b158 100644 --- a/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs +++ b/src/Features/Core/Portable/SpellCheck/AbstractSpellCheckCodeFixProvider.cs @@ -116,7 +116,8 @@ private async Task CreateSpellCheckCodeIssueAsync( var originalOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var options = originalOptions .WithChangedOption(CompletionOptions.SnippetsBehavior, document.Project.Language, SnippetsRule.NeverInclude) - .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, document.Project.Language, false); + .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, document.Project.Language, false) + .WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, false); var completionList = await service.GetCompletionsAsync( document, nameToken.SpanStart, options: options, cancellationToken: cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs index b380a43c036fb..e5259c283166b 100644 --- a/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs +++ b/src/Features/Core/Portable/UnusedReferences/UnusedReferencesRemover.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.UnusedReferences @@ -26,22 +25,32 @@ internal static class UnusedReferencesRemover }; public static async Task> GetUnusedReferencesAsync( - Project project, + Solution solution, + string projectFilePath, ImmutableArray references, CancellationToken cancellationToken) { - // Create a lookup of used assembly paths - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - if (compilation is null) + var projects = solution.Projects + .Where(project => projectFilePath.Equals(project.FilePath, System.StringComparison.OrdinalIgnoreCase)); + + HashSet usedAssemblyFilePaths = new(); + + foreach (var project in projects) { - return ImmutableArray.Empty; - } + // Create a lookup of used assembly paths + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + if (compilation is null) + { + continue; + } - var usedAssemblyReferences = compilation.GetUsedAssemblyReferences(cancellationToken); - HashSet usedAssemblyFilePaths = new(usedAssemblyReferences - .OfType() - .Select(reference => reference.FilePath) - .WhereNotNull()); + var usedAssemblyReferences = compilation.GetUsedAssemblyReferences(cancellationToken); + + usedAssemblyFilePaths.AddRange(usedAssemblyReferences + .OfType() + .Select(reference => reference.FilePath) + .WhereNotNull()); + } return GetUnusedReferences(usedAssemblyFilePaths, references); } @@ -192,16 +201,17 @@ internal static ImmutableArray GetAllCompilationAssemblies(ReferenceInfo .ToImmutableArray(); } - public static async Task UpdateReferencesAsync( - Project project, + public static async Task UpdateReferencesAsync( + Solution solution, + string projectFilePath, ImmutableArray referenceUpdates, CancellationToken cancellationToken) { - var referenceCleanupService = project.Solution.Workspace.Services.GetRequiredService(); + var referenceCleanupService = solution.Workspace.Services.GetRequiredService(); - await ApplyReferenceUpdatesAsync(referenceCleanupService, project.FilePath!, referenceUpdates, cancellationToken).ConfigureAwait(true); + await ApplyReferenceUpdatesAsync(referenceCleanupService, projectFilePath, referenceUpdates, cancellationToken).ConfigureAwait(true); - return project.Solution.Workspace.CurrentSolution.GetRequiredProject(project.Id); + return solution.Workspace.CurrentSolution; } internal static async Task ApplyReferenceUpdatesAsync( diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index a4e4256d2e0c7..5c6a209574021 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -90,6 +90,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Přidat název elementu řazené kolekce členů {0} + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. Přidání prvku {0} do metody rozhraní zabrání v pokračování relace ladění. @@ -125,6 +130,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Podmínky alternativního výrazu nezachytávají a nejde je pojmenovat. This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' Očekávaná úloha vrací {0}. @@ -205,6 +215,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Pokud se změní {0} na {1}, relace ladění nebude moct pokračovat, protože dojde ke změně tvaru stavového počítače. + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style Nakonfigurovat styl kódu {0} @@ -2336,6 +2351,11 @@ Pokud se specifikátor formátu H použije bez dalších specifikátorů vlastn Specifikátor vlastního formátu HH reprezentuje hodinu jako číslo od 00 do 23. To znamená, že hodina je reprezentována ve 24hodinovém formátu, který začíná na nule a počítá hodiny od půlnoci. Hodina s jednou číslicí se formátuje s nulou na začátku. + + code + code + + date separator oddělovač data @@ -2492,6 +2512,11 @@ Specifikátor standardního formátu f představuje kombinaci vzorů dlouhého d Specifikátor standardního formátu T představuje řetězec vlastního formátu data a času, který se definuje pomocí vlastnosti DateTimeFormatInfo.LongTimePattern konkrétní jazykové verze. Například řetězec vlastního formátu pro invariantní jazykovou verzi je HH:mm:ss. + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) minuta (1–2 číslice) @@ -2944,11 +2969,6 @@ Pokud se specifikátor formátu g použije bez dalších specifikátorů vlastn Aktualizace komplexního příkazu, který obsahuje výraz await, znemožní relaci ladění pokračovat. - - Changing visibility of a constructor will prevent the debug session from continuing. - Změna viditelnosti konstruktoru znemožní relaci ladění pokračovat. - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. Zachycení proměnné {0}, která se nezachytila dřív, zabrání v pokračování relace ladění. @@ -3155,8 +3175,8 @@ Pokud se specifikátor formátu g použije bez dalších specifikátorů vlastn - Deleting '{0}' will prevent the debug session from continuing. - Odstranění prvku {0} zabrání v pokračování relace ladění. + Deleting {0} will prevent the debug session from continuing. + Odstranění prvku {0} zabrání v pokračování relace ladění. @@ -3174,11 +3194,6 @@ Pokud se specifikátor formátu g použije bez dalších specifikátorů vlastn Odstranění těla metody zabrání v pokračování relace ladění. - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - Aktivní příkaz je odstraněný z jeho původní metody. Pro pokračování musíte vzít zpátky změny nebo restartovat ladicí relaci. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Aktualizace modifikátoru async nebo iterator kolem aktivního příkazu bude bránit relaci ladění v tom, aby pokračovala. @@ -3209,11 +3224,6 @@ Pokud se specifikátor formátu g použije bez dalších specifikátorů vlastn Modifikace inicializátoru pro {0} v obecném typu zabrání v pokračování relace ladění. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - Modifikace inicializátoru pro {0} v částečném typu zabrání v pokračování relace ladění. - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. Přidáním konstruktoru do typu s inicializátorem pole nebo vlastnosti obsahujícím anonymní funkci zabráníte ladicí relaci v pokračování. @@ -3265,8 +3275,8 @@ Pokud se specifikátor formátu g použije bez dalších specifikátorů vlastn - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - Odebrání prvku {0}, který obsahuje aktivní příkaz, zabrání v pokračování relace ladění. + Removing {0} that contains an active statement will prevent the debug session from continuing. + Odebrání prvku {0}, který obsahuje aktivní příkaz, zabrání v pokračování relace ladění. @@ -3660,6 +3670,11 @@ When this standard format specifier is used, the formatting or parsing operation Když se tento specifikátor standardního formátu použije, operace formátování nebo parsování vždy použije invariantní jazykovou verzi. + + static constructor + static constructor + + 'symbol' cannot be a namespace. 'Symbol nemůže být obor názvů. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index ce77750806a5e..b5ce297a5a12b 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -90,6 +90,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Tupelelementnamen "{0}" hinzufügen + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. Das Hinzufügen von "{0}" zu einer Schnittstellenmethode verhindert das Fortsetzen der Debugsitzung. @@ -125,6 +130,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Wechselbedingungen werden nicht erfasst und können nicht benannt werden. This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' Erwartete Aufgabe gibt "{0}" zurück @@ -205,6 +215,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Die Änderung von "{0}" in "{1}" verhindert, dass die Debugsitzung fortgesetzt wird, weil sich dadurch die Form des Zustandsautomaten verändert. + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style Codeformat "{0}" konfigurieren @@ -2336,6 +2351,11 @@ Bei Verwendung des Formatbezeichners "H" ohne weitere benutzerdefinierte Formatb Der benutzerdefinierte Formatbezeichner "HH" (plus beliebig viele zusätzliche H-Bezeichner) repräsentiert die Stunde als eine Zahl zwischen 00 und 23, d. h., die Stunde wird in einem nullbasierten 24-Stunden-Format dargestellt, bei dem die Stunden seit Mitternacht gezählt werden. Ein einstelliger Stundenwert wird mit einer führenden Null formatiert. + + code + code + + date separator Datumstrennzeichen @@ -2492,6 +2512,11 @@ Der Standardformatbezeichner "f" repräsentiert eine Kombination aus den Mustern Der Standardformatbezeichner "T" repräsentiert eine benutzerdefinierte Datums- und Uhrzeitformatzeichenfolge, die durch die DateTimeFormatInfo.LongTimePattern-Eigenschaft einer bestimmten Kultur definiert ist. Die benutzerdefinierte Formatzeichenfolge für die invariante Kultur lautet beispielsweise "HH:mm:ss". + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) Minute (1–2 Stellen) @@ -2944,11 +2969,6 @@ Bei Verwendung des Formatbezeichners "g" ohne weitere benutzerdefinierte Formatb Beim Ändern von komplexen Anweisungen, die einen "await"-Ausdruck enthalten, wird die Debuggingsitzung unterbrochen. - - Changing visibility of a constructor will prevent the debug session from continuing. - Bei Änderungen an der Sichtbarkeit von Konstruktoren wird die Debuggingsitzung unterbrochen. - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. Nach dem Erfassen der zuvor noch nicht erfassten Variable "{0}" kann die Debugsitzung nicht mehr fortgesetzt werden. @@ -3155,8 +3175,8 @@ Bei Verwendung des Formatbezeichners "g" ohne weitere benutzerdefinierte Formatb - Deleting '{0}' will prevent the debug session from continuing. - Durch Löschen von "{0}" wird verhindert, dass die Debuggingsitzung fortgesetzt wird. + Deleting {0} will prevent the debug session from continuing. + Durch Löschen von "{0}" wird verhindert, dass die Debuggingsitzung fortgesetzt wird. @@ -3174,11 +3194,6 @@ Bei Verwendung des Formatbezeichners "g" ohne weitere benutzerdefinierte Formatb Durch Löschen eines Methodenkörpers wird verhindert, dass eine Debuggingsitzung fortgesetzt wird. - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - Eine aktive Anweisung wurde aus der ursprünglichen Methode entfernt. Sie müssen Ihre Änderungen rückgängig machen, um die Debuggingsitzung fortzusetzen oder neu zu starten. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Eine asynchrone Aktualisierung oder ein iteratormodifizierer um eine aktive Anweisung verhindert, dass die Debugsitzung fortgesetzt wird. @@ -3209,11 +3224,6 @@ Bei Verwendung des Formatbezeichners "g" ohne weitere benutzerdefinierte Formatb Durch Ändern des Initialisierers von "{0}" in einem generischen Typen wird verhindert, dass die Debuggingsitzung fortgesetzt wird. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - Durch Ändern des Initialisierers von "{0}" in einem partiellen Typ wird verhindert, dass die Debuggingsitzung fortgesetzt wird. - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. Das Hinzufügen eines Konstruktors zu einem Typ mit einem Feld- oder Eigenschafteninitialisierer, der eine anonyme Funktion enthält, verhindert das Fortsetzen der Debugsitzung. @@ -3265,8 +3275,8 @@ Bei Verwendung des Formatbezeichners "g" ohne weitere benutzerdefinierte Formatb - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - Durch Entfernen von "{0}", das eine aktive Anweisung enthält, wird verhindert, dass die Debuggingsitzung fortgesetzt wird. + Removing {0} that contains an active statement will prevent the debug session from continuing. + Durch Entfernen von "{0}", das eine aktive Anweisung enthält, wird verhindert, dass die Debuggingsitzung fortgesetzt wird. @@ -3660,6 +3670,11 @@ Der Zweck des Formatbezeichners "s" besteht darin, Ergebniszeichenfolgen zu erze Bei Verwendung dieses Standardformatbezeichners wird zur Formatierung oder Analyse immer die invariante Kultur verwendet. + + static constructor + static constructor + + 'symbol' cannot be a namespace. '"symbol" kann kein Namespace sein. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 02f324b293deb..e626a716f03db 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -90,6 +90,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Agregar el nombre del elemento de tupla "{0}" + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. Agregar "{0}" a un método de interfaz impedirá que continúe la sesión de depuración. @@ -125,6 +130,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Las condiciones de alternancia no se captan y se les puede poner un nombre This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' La tarea esperada devuelve "{0}". @@ -205,6 +215,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Si se cambia "{0}" a "{1}", la sesión de depuración no podrá continuar porque cambia la forma de la máquina de estados. + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style Configurar el estilo de código de {0} @@ -2336,6 +2351,11 @@ Si el especificador de formato "H" se usa sin otros especificadores de formato p El especificador de formato personalizado "HH" (más cualquier número de especificadores "H" adicionales) representa la hora como un número de 00 a 23, es decir, mediante un reloj de 24 horas de base cero que cuenta las horas desde la medianoche. El formato de una hora de un solo dígito es con un cero inicial. + + code + code + + date separator separador de fecha @@ -2492,6 +2512,11 @@ El especificador de formato estándar "f" representa una combinación de los pat El especificador de formato estándar "T" representa una cadena de formato de fecha y hora personalizado que está definida por la propiedad DateTimeFormatInfo.LongTimePattern de una referencia cultural específica. Por ejemplo, la cadena de formato personalizado para la referencia cultural invariable es "HH:mm:ss". + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) minuto (1-2 dígitos) @@ -2944,11 +2969,6 @@ Si el especificador de formato "g" se usa sin otros especificadores de formato p Si se actualiza una instrucción compleja que contiene una expresión await, la sesión de depuración no podrá continuar. - - Changing visibility of a constructor will prevent the debug session from continuing. - Si se cambia la visibilidad de un constructor, la sesión de depuración no podrá continuar. - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. La captura de una variable "{0}" que no se había capturado antes impedirá que continúe la sesión de depuración. @@ -3155,8 +3175,8 @@ Si el especificador de formato "g" se usa sin otros especificadores de formato p - Deleting '{0}' will prevent the debug session from continuing. - Eliminar '{0}' impedirá que continúe la sesión de depuración. + Deleting {0} will prevent the debug session from continuing. + Eliminar '{0}' impedirá que continúe la sesión de depuración. @@ -3174,11 +3194,6 @@ Si el especificador de formato "g" se usa sin otros especificadores de formato p Eliminar un cuerpo de método impedirá que continúe la sesión de depuración. - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - Se quitó una instrucción activa de su método original. Debe revertir los cambios para continuar o reiniciar la sesión de depuración. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. La actualización del modificador async o iterator en una instrucción activa impedirá que la sesión de depuración continúe. @@ -3209,11 +3224,6 @@ Si el especificador de formato "g" se usa sin otros especificadores de formato p Modificar el inicializador de '{0}' en un tipo genérico impedirá que continúe la sesión de depuración. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - Modificar el inicializador de '{0}' en un tipo parcial impedirá que continúe la sesión de depuración. - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. Añadir un constructor a un tipo con un inicializador de campo o propiedad que contiene una función anónima detendrá la sesión de depuración. @@ -3265,8 +3275,8 @@ Si el especificador de formato "g" se usa sin otros especificadores de formato p - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - Quitar '{0}' que contiene una instrucción activa impedirá que continúe la sesión de depuración. + Removing {0} that contains an active statement will prevent the debug session from continuing. + Quitar '{0}' que contiene una instrucción activa impedirá que continúe la sesión de depuración. @@ -3660,6 +3670,11 @@ La finalidad del especificador de formato "s" es producir cadenas de resultados Cuando se usa este especificador de formato estándar, la operación de formato o análisis utiliza siempre la referencia cultural invariable. + + static constructor + static constructor + + 'symbol' cannot be a namespace. 'símbolo' no puede ser un espacio de nombres. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index afcff34a0a291..5cfc63f66348f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -90,6 +90,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Ajouter le nom d'élément tuple '{0}' + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. L'ajout de '{0}' à une méthode d'interface empêche la session de débogage de se poursuivre. @@ -125,6 +130,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Les conditions d'alternance n'effectuent pas de capture et ne peuvent pas être nommées This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' La tâche attendue retourne '{0}' @@ -205,6 +215,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Le remplacement de '{0}' par '{1}' va empêcher la session de débogage de se poursuivre, car cela change la forme de la machine à états. + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style Configurer le style de code {0} @@ -2336,6 +2351,11 @@ Si le spécificateur de format "H" est utilisé sans autres spécificateurs de f Le spécificateur de format personnalisé "HH" (plus n'importe quel nombre de spécificateurs "H" supplémentaires) représente les heures sous la forme d'un nombre compris entre 00 et 23. En d'autres termes, les heures sont représentées par une horloge de 24 heures de base zéro, qui compte les heures écoulées depuis minuit. Une heure à un chiffre est présentée dans un format qui comporte un zéro de début. + + code + code + + date separator séparateur de date @@ -2492,6 +2512,11 @@ Le spécificateur de format standard "f" représente une combinaison des modèle Le spécificateur de format standard "T" représente une chaîne de format de date et d'heure personnalisée, qui est définie par la propriété DateTimeFormatInfo.LongTimePattern d'une culture spécifique. Par exemple, la chaîne de format personnalisée pour la culture invariante est "HH:mm:ss". + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) minute (1 à 2 chiffres) @@ -2944,11 +2969,6 @@ Si le spécificateur de format "g" est utilisé sans autres spécificateurs de f La mise à jour d'une instruction complexe contenant une expression await empêche la session de débogage de se poursuivre. - - Changing visibility of a constructor will prevent the debug session from continuing. - Le changement de visibilité d'un constructeur empêche la session de débogage de se poursuivre. - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. La capture de la variable '{0}' qui n'a pas déjà été capturée empêche la session de débogage de se poursuivre. @@ -3155,8 +3175,8 @@ Si le spécificateur de format "g" est utilisé sans autres spécificateurs de f - Deleting '{0}' will prevent the debug session from continuing. - La suppression de '{0}' empêche la session de débogage de se poursuivre. + Deleting {0} will prevent the debug session from continuing. + La suppression de '{0}' empêche la session de débogage de se poursuivre. @@ -3174,11 +3194,6 @@ Si le spécificateur de format "g" est utilisé sans autres spécificateurs de f La suppression d'un corps de méthode empêche la session de débogage de se poursuivre. - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - Une instruction active a été supprimée de sa méthode d'origine. Annulez vos modifications si vous voulez continuer ou redémarrez la session de débogage. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. La mise à jour d'un modificateur async ou iterator autour d'une instruction active empêche la session de débogage de se poursuivre. @@ -3209,11 +3224,6 @@ Si le spécificateur de format "g" est utilisé sans autres spécificateurs de f La modification de l'initialiseur de '{0}' dans un type générique empêche la session de débogage de se poursuivre. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - La modification de l'initialiseur de '{0}' dans un type partiel empêche la session de débogage de se poursuivre. - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. L'ajout d'un constructeur à un type avec un initialiseur de champ ou de propriété contenant une fonction anonyme empêche la poursuite de la session de débogage. @@ -3265,8 +3275,8 @@ Si le spécificateur de format "g" est utilisé sans autres spécificateurs de f - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - La suppression de '{0}' contenant une instruction active empêche la session de débogage de se poursuivre. + Removing {0} that contains an active statement will prevent the debug session from continuing. + La suppression de '{0}' contenant une instruction active empêche la session de débogage de se poursuivre. @@ -3660,6 +3670,11 @@ Le spécificateur de format "s" a pour but de produire des chaînes triées de m Quand ce spécificateur de format standard est utilisé, l'opération qui consiste à appliquer un format ou à exécuter une analyse utilise toujours la culture invariante. + + static constructor + static constructor + + 'symbol' cannot be a namespace. 'symbol' ne peut pas être un espace de noms. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index c6acb049831bc..f58fc1b2fd4a8 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -90,6 +90,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Aggiungi il nome dell'elemento tupla '{0}' + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. Se si aggiunge '{0}' in un metodo di interfaccia, la sessione di debug non potrà continuare. @@ -125,6 +130,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Le condizioni di alternanza non consentono l'acquisizione e non possono essere denominate This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' L'attività attesa restituisce '{0}' @@ -205,6 +215,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Se si modifica '{0}' in '{1}', la sessione di debug non potrà continuare perché modifica la forma della macchina a stati. + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style Configura lo stile del codice di {0} @@ -2336,6 +2351,11 @@ Se l'identificatore di formato "H" viene usato senza altri identificatori di for L'identificatore di formato personalizzato "HH" (più qualsiasi numero di identificatori "H" aggiuntivi) rappresenta l'ora come numero compreso tra 00 e 23, ovvero l'ora rappresentata nell'orario in formato 24 ore a base zero in base al quale il conteggio riparte da mezzanotte. Un'ora costituita da una singola cifra viene formattata con uno zero iniziale. + + code + code + + date separator Separatore di data @@ -2492,6 +2512,11 @@ L'identificatore di formato standard "f" rappresenta una combinazione degli sche L'identificatore di formato standard "T" rappresenta una stringa di formato di data e ora personalizzata definita dalla proprietà DateTimeFormatInfo.LongTimePattern di impostazioni cultura specifiche. Ad esempio, la stringa di formato personalizzata per le impostazioni cultura inglese non dipendenti da paese/area geografica è "HH:mm:ss". + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) Minuto (1-2 cifre) @@ -2944,11 +2969,6 @@ Se l'identificatore di formato "g" viene usato senza altri identificatori di for Se si aggiorna un'istruzione complessa contenente un'espressione await, la sessione di debug non potrà continuare. - - Changing visibility of a constructor will prevent the debug session from continuing. - Se si modifica la visibilità di un costruttore, la sessione di debug non potrà continuare. - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. Se si acquisisce la variabile '{0}' che non è stata ancora acquisita, la sessione di debug non potrà continuare. @@ -3155,8 +3175,8 @@ Se l'identificatore di formato "g" viene usato senza altri identificatori di for - Deleting '{0}' will prevent the debug session from continuing. - Se si elimina '{0}', la sessione di debug non potrà continuare. + Deleting {0} will prevent the debug session from continuing. + Se si elimina '{0}', la sessione di debug non potrà continuare. @@ -3174,11 +3194,6 @@ Se l'identificatore di formato "g" viene usato senza altri identificatori di for Se si elimina un corpo del metodo, la sessione di debug non potrà continuare. - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - Un'istruzione attiva è stata rimossa dal metodo originale. Annullare le modifiche per continuare oppure riavviare la sessione di debug. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Se si aggiorna un modificatore iterator o async in prossimità di un'istruzione attiva, la sessione di debug non potrà continuare. @@ -3209,11 +3224,6 @@ Se l'identificatore di formato "g" viene usato senza altri identificatori di for Se si modifica l'inizializzatore di '{0}' in un tipo generico, la sessione di debug non potrà continuare. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - Se si modifica l'inizializzatore di '{0}' in un tipo parziale, la sessione di debug non potrà continuare. - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. Se aggiunge un costruttore a un tipo con un inizializzatore di campo o proprietà che contiene una funzione anonima, la sessione di debug non potrà continuare. @@ -3265,8 +3275,8 @@ Se l'identificatore di formato "g" viene usato senza altri identificatori di for - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - Se si rimuove '{0}', che contiene un'istruzione attiva, la sessione di debug non potrà continuare. + Removing {0} that contains an active statement will prevent the debug session from continuing. + Se si rimuove '{0}', che contiene un'istruzione attiva, la sessione di debug non potrà continuare. @@ -3660,6 +3670,11 @@ L'identificatore di formato "s" ha lo scopo di produrre stringhe di risultati or Quando si usa questo identificatore di formato standard, la formattazione o l'operazione di analisi usa sempre le impostazioni cultura inglese non dipendenti da paese/area geografica. + + static constructor + static constructor + + 'symbol' cannot be a namespace. 'L'elemento 'symbol' non può essere uno spazio dei nomi. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 19d0d6368d0ec..725ec5181ffbb 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -90,6 +90,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma タプル要素名 '{0}' を追加します + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. '{0}' をインターフェイス メソッドに追加すると、デバッグ セッションは続行されません。 @@ -125,6 +130,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 選択条件が捕捉されないため、名前を指定できません This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' 待機中のタスクから '{0}' が返されました @@ -205,6 +215,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}' から '{1}' に変更すると、ステート マシンの形状が変わるため、デバッグ セッションは続行されません。 + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style {0} コード スタイルの構成 @@ -2336,6 +2351,11 @@ If the "H" format specifier is used without other custom format specifiers, it's "HH" カスタム書式指定子 (任意の数の "H" 指定子を追加できます) は、時を 00 から 23 の数字で表します。つまり、午前 0 時からの時間をカウントする、ゼロから始まる 24 時間制で時が表されます。1 桁の時は、先行ゼロ付きで書式設定されます。 + + code + code + + date separator 日付の区切り記号 @@ -2492,6 +2512,11 @@ The "f" standard format specifier represents a combination of the long date ("D" "T" 標準書式指定子は、特定のカルチャの DateTimeFormatInfo.LongTimePattern プロパティで定義されているカスタム日時書式指定文字列を表します。たとえば、インバリアント カルチャのカスタム書式指定文字列は "HH:mm:ss" です。 + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) 分 (1 - 2 桁) @@ -2944,11 +2969,6 @@ If the "g" format specifier is used without other custom format specifiers, it's Await 式を含む複雑なステートメントを更新すると、デバッグ セッションを続行できなくなります。 - - Changing visibility of a constructor will prevent the debug session from continuing. - コンストラクターの表示範囲を変更すると、デバッグ セッションを続行できなくなります。 - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. 以前にキャプチャされたことのない変数 '{0}' をキャプチャすると、デバッグ セッションは続行されません。 @@ -3155,8 +3175,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Deleting '{0}' will prevent the debug session from continuing. - '{0}' を削除すると、デバッグ セッションは続行されません。 + Deleting {0} will prevent the debug session from continuing. + '{0}' を削除すると、デバッグ セッションは続行されません。 @@ -3174,11 +3194,6 @@ If the "g" format specifier is used without other custom format specifiers, it's メソッド本体を削除すると、デバッグ セッションは続行されません。 - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - アクティブ ステートメントは元のメソッドから削除されました。変更を元に戻して続行するか、またはデバッグ セッションを再開してください。 - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. アクティブ ステートメントの前後の async 修飾子または iterator 修飾子を更新すると、デバッグ セッションが継続しなくなります。 @@ -3209,11 +3224,6 @@ If the "g" format specifier is used without other custom format specifiers, it's ジェネリック型の '{0}' の初期化子を変更すると、デバッグ セッションは続行されません。 - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - 部分型の '{0}' の初期化子を変更すると、デバッグ セッションは続行されません。 - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. 匿名関数を含むフィールドまたはプロパティの初期化子を持つ型にコンストラクターを追加すると、デバッグ セッションを続行できなくなります。 @@ -3265,8 +3275,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - アクティブ ステートメンを含む '{0}' を削除すると、デバッグ セッションは続行されません。 + Removing {0} that contains an active statement will prevent the debug session from continuing. + アクティブ ステートメンを含む '{0}' を削除すると、デバッグ セッションは続行されません。 @@ -3660,6 +3670,11 @@ When this standard format specifier is used, the formatting or parsing operation この標準書式指定子が使用されている場合、書式設定操作または解析操作では常にインバリアント カルチャが使用されます。 + + static constructor + static constructor + + 'symbol' cannot be a namespace. 'symbol' は名前空間にすることはできません。 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index b4ed0092e732d..b36e3998c6f14 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -90,6 +90,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 튜플 요소 이름 '{0}' 추가 + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. 인터페이스 메서드에 '{0}'을(를) 추가하면 디버그 세션을 계속할 수 없습니다. @@ -125,6 +130,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 교체 조건은 캡처하지 않고 이름을 지정할 수 없습니다. This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' 대기된 작업에서 '{0}'이(가) 반환됨 @@ -205,6 +215,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}'을(를) '{1}'(으)로 변경하면 상태 시스템의 셰이프가 변경되므로 디버그 세션을 계속할 수 없습니다. + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style {0} 코드 스타일 구성 @@ -2336,6 +2351,11 @@ If the "H" format specifier is used without other custom format specifiers, it's "HH" 사용자 지정 형식 지정자(및 임의 개수의 추가 "H" 지정자)는 시간을 00부터 23까지의 숫자로 나타냅니다. 즉, 시간은 자정부터 경과한 시간을 계산하는 0 기반 24시간제로 표현됩니다. 한 자릿수 시간은 앞에 0이 있는 형식으로 지정됩니다. + + code + code + + date separator 날짜 구분 기호 @@ -2492,6 +2512,11 @@ The "f" standard format specifier represents a combination of the long date ("D" "T" 표준 형식 지정자는 특정 문화권의 DateTimeFormatInfo.LongTimePattern 속성으로 정의되는 사용자 지정 날짜 및 시간 형식 문자열을 나타냅니다. 예를 들어, 고정 문화권의 사용자 지정 형식 문자열은 "HH:mm:ss"입니다. + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) 분(1~2자리) @@ -2944,11 +2969,6 @@ If the "g" format specifier is used without other custom format specifiers, it's await 식이 포함된 복합 문을 업데이트하면 디버그 세션을 계속할 수 없습니다. - - Changing visibility of a constructor will prevent the debug session from continuing. - 생성자 표시 여부를 변경하면 디버그 세션을 계속할 수 없습니다. - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. 이전에 캡처되지 않았던 '{0}' 변수를 캡처하면 디버그 세션을 계속할 수 없습니다. @@ -3155,8 +3175,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Deleting '{0}' will prevent the debug session from continuing. - '{0}'을(를) 삭제하면 디버그 세션을 계속할 수 없습니다. + Deleting {0} will prevent the debug session from continuing. + '{0}'을(를) 삭제하면 디버그 세션을 계속할 수 없습니다. @@ -3174,11 +3194,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 메서드 본문을 삭제하면 디버그 세션을 계속할 수 없습니다. - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - 원래 메서드에서 활성 문이 제거되었습니다. 변경 내용을 취소하고 계속하거나 디버깅 세션을 다시 시작해야 합니다. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. 활성 문 주위의 async 또는 iterator 한정자를 업데이트하면 디버그 세션이 계속되지 않습니다. @@ -3209,11 +3224,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 제네릭 형식에서 '{0}'의 이니셜라이저를 수정하면 디버그 세션을 계속할 수 없습니다. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - 부분 형식에서 '{0}'의 이니셜라이저를 수정하면 디버그 세션을 계속할 수 없습니다. - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. 익명 함수를 포함하는 필드 또는 속성 이니셜라이저가 있는 형식에 생성자를 추가하면 디버그 세션을 계속할 수 없습니다. @@ -3265,8 +3275,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - 활성 문이 포함된 '{0}'을(를) 제거하면 디버그 세션을 계속할 수 없습니다. + Removing {0} that contains an active statement will prevent the debug session from continuing. + 활성 문이 포함된 '{0}'을(를) 제거하면 디버그 세션을 계속할 수 없습니다. @@ -3660,6 +3670,11 @@ When this standard format specifier is used, the formatting or parsing operation 이 표준 형식 지정자를 사용하면 형식 지정 또는 구문 분석 연산에서 항상 고정 문화권이 사용됩니다. + + static constructor + static constructor + + 'symbol' cannot be a namespace. '기호'는 네임스페이스일 수 없습니다. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 2b2ea5d38aa33..c65d7f325db43 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -90,6 +90,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Dodaj nazwę elementu krotki „{0}” + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. Dodanie elementu „{0}” do metody interfejsu uniemożliwi kontynuowanie sesji debugowania. @@ -125,6 +130,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Warunki alternatywne nie przechwytują i nie mogą być nazywane This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' Zadanie, na które oczekiwano, zwraca wartość „{0}” @@ -205,6 +215,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Zmiana elementu „{0}” na „{1}” uniemożliwi kontynuowanie sesji debugowania, ponieważ powoduje zmianę kształtu automatu stanów. + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style Konfiguruj styl kodu {0} @@ -2336,6 +2351,11 @@ Jeśli specyfikator formatu „H” zostanie użyty bez innych indywidualnych sp Indywidualny specyfikator formatu „HH” (wraz z dowolną liczbą dodatkowych specyfikatorów „H”) reprezentuje godzinę jako liczbę z zakresu od 00 do 23; tzn. godzina jest reprezentowana przez 24-godzinny zegar zaczynający się od zera i liczący godziny od północy. Godzina 1-cyfrowa jest wyświetlana w formacie z wiodącym zerem. + + code + code + + date separator separator daty @@ -2492,6 +2512,11 @@ Standardowy specyfikator formatu „f” reprezentuje połączenie wzorców dłu Standardowy specyfikator formatu „T” reprezentuje niestandardowy ciąg formatu daty i godziny zdefiniowany przez właściwość DateTimeFormatInfo.LongTimePattern konkretnej kultury. Na przykład niestandardowy ciąg formatu dla kultury niezmiennej to „HH:mm:ss”. + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) minuta (1-2 cyfry) @@ -2944,11 +2969,6 @@ Jeśli specyfikator formatu „g” jest używany bez innych niestandardowych sp Zaktualizowanie instrukcji złożonej zawierającej wyrażenie await uniemożliwi kontynuowanie sesji debugowania. - - Changing visibility of a constructor will prevent the debug session from continuing. - Zmiana widoczności konstruktora uniemożliwi kontynuowanie sesji debugowania. - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. Przechwycenie zmiennej „{0}”, która nie została przechwycona wcześniej, uniemożliwi kontynuowanie sesji debugowania. @@ -3155,8 +3175,8 @@ Jeśli specyfikator formatu „g” jest używany bez innych niestandardowych sp - Deleting '{0}' will prevent the debug session from continuing. - Usunięcie elementu „{0}” uniemożliwi kontynuowanie sesji debugowania. + Deleting {0} will prevent the debug session from continuing. + Usunięcie elementu „{0}” uniemożliwi kontynuowanie sesji debugowania. @@ -3174,11 +3194,6 @@ Jeśli specyfikator formatu „g” jest używany bez innych niestandardowych sp Usunięcie treści metody uniemożliwi kontynuowanie sesji debugowania. - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - Aktywna instrukcja została usunięta ze swojej oryginalnej metody. Musisz cofnąć zmiany, aby kontynuować, lub uruchomić ponownie sesję debugowania. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Aktualizowanie modyfikatora asynchronicznego lub powiązanego z iteratorem w pobliżu aktywnej instrukcji uniemożliwi kontynuowanie sesji debugowania. @@ -3209,11 +3224,6 @@ Jeśli specyfikator formatu „g” jest używany bez innych niestandardowych sp Modyfikacja inicjatora elementu „{0}” w typie ogólnym uniemożliwi kontynuowanie sesji debugowania. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - Modyfikacja inicjatora elementu „{0}” w typie częściowym uniemożliwi kontynuowanie sesji debugowania. - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. Dodanie konstruktora do typu z inicjatorem pola lub właściwości zawierającego funkcję anonimową uniemożliwi kontynuowanie sesji debugowania. @@ -3265,8 +3275,8 @@ Jeśli specyfikator formatu „g” jest używany bez innych niestandardowych sp - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - Usunięcie elementu „{0}” zawierającego aktywną instrukcję uniemożliwi kontynuowanie sesji debugowania. + Removing {0} that contains an active statement will prevent the debug session from continuing. + Usunięcie elementu „{0}” zawierającego aktywną instrukcję uniemożliwi kontynuowanie sesji debugowania. @@ -3660,6 +3670,11 @@ Specyfikator formatu „s” jest przeznaczony do tworzenia ciągów wyniku, kt Gdy jest używany ten standardowy specyfikator formatu, operacja formatowania lub analizowania zawsze używa kultury niezmiennej. + + static constructor + static constructor + + 'symbol' cannot be a namespace. 'Element „symbol” nie może być przestrzenią nazw. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 39dab409b6bfe..af5616b188b04 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -90,6 +90,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Adicionar o nome do elemento de tupla '{0}' + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. Adicionar '{0}' a um método de interface impedirá que a sessão de depuração continue. @@ -125,6 +130,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Condições de alternância não capturam e não podem ser nomeadas This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' A tarefa esperada retorna '{0}' @@ -205,6 +215,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Alterar '{0}' para '{1}' impedirá a continuação da sessão de depuração porque altera a forma da máquina de estado. + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style Configurar estilo de código de {0} @@ -2336,6 +2351,11 @@ Se o especificador de formato "H" for usado sem outros especificadores de format O especificador de formato personalizado "HH" (mais qualquer número de especificadores "H" adicionais) representa a hora como um número de 00 a 23. Ou seja, a hora é representada por um relógio de 24 horas com base em zero que conta as horas desde a meia-noite. Uma hora de dígito único é formatada com um zero à esquerda. + + code + code + + date separator separador de data @@ -2492,6 +2512,11 @@ O especificador de formato padrão "f" representa uma combinação de padrões d O especificador de formato padrão "T" representa uma cadeia de caracteres de formato personalizado de data e hora que é definida pela propriedade DateTimeFormatInfo.LongTimePattern de uma cultura específica. Por exemplo, a cadeia de caracteres de formato personalizado para a cultura invariável é "HH:mm:ss". + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) minuto (1-2 dígitos) @@ -2944,11 +2969,6 @@ Se o especificador de formato "g" for usado sem outros especificadores de format Atualizar uma instrução complexa contendo uma expressão aguardar impedirá que a sessão de depuração continue. - - Changing visibility of a constructor will prevent the debug session from continuing. - Alteração da visibilidade de um construtor impedirá a sessão de depuração de continuar. - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. A captura da variável '{0}' que não havia sido capturada antes impedirá a sessão de depuração de continuar. @@ -3155,8 +3175,8 @@ Se o especificador de formato "g" for usado sem outros especificadores de format - Deleting '{0}' will prevent the debug session from continuing. - Excluir "{0}" impedirá que a sessão de depuração continue. + Deleting {0} will prevent the debug session from continuing. + Excluir "{0}" impedirá que a sessão de depuração continue. @@ -3174,11 +3194,6 @@ Se o especificador de formato "g" for usado sem outros especificadores de format Excluir um corpo de método impedirá que a sessão de depuração continue. - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - Uma instrução ativa foi removida de seu método original. Você deve reverter as alterações para continuar ou reiniciar a sessão de depuração. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. A atualização do modificador "async" ou "iterator" em torno de uma instrução ativa impedirá a sessão de depuração de continuar. @@ -3209,11 +3224,6 @@ Se o especificador de formato "g" for usado sem outros especificadores de format Modificar o inicializador de "{0}" em um tipo genérico impedirá que a sessão de depuração continue. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - Modificar o inicializador de "{0}" em um tipo parcial impedirá que a sessão de depuração continue. - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. Adicionar um construtor a um tipo com um campo ou inicializador que propriedade com função anônima impedirá que a sessão de depuração continue. @@ -3265,8 +3275,8 @@ Se o especificador de formato "g" for usado sem outros especificadores de format - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - Remover "{0}" que contém uma instrução ativa impedirá que a sessão de depuração continue. + Removing {0} that contains an active statement will prevent the debug session from continuing. + Remover "{0}" que contém uma instrução ativa impedirá que a sessão de depuração continue. @@ -3660,6 +3670,11 @@ O objetivo do especificador de formato "s" é produzir cadeias de caracteres de Quando esse especificador de formato padrão é usado, a operação de análise ou formatação sempre usa a cultura invariável. + + static constructor + static constructor + + 'symbol' cannot be a namespace. "símbolo" não pode ser um namespace. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index e43964cfd9924..d88360bb1f41f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -90,6 +90,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Добавить имя элемента кортежа "{0}" + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. Добавление "{0}" в метод интерфейса сделает продолжение сеанса отладки невозможным. @@ -125,6 +130,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Условия чередования не выполняют запись, и им невозможно присвоить имя This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' Ожидаемая задача возвращает "{0}". @@ -205,6 +215,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Изменение "{0}" на "{1}" сделает продолжение сеанса отладки невозможным, так как меняет форму конечной машины. + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style Настройка стиля кода {0} @@ -2336,6 +2351,11 @@ If the "H" format specifier is used without other custom format specifiers, it's Описатель пользовательского формата "HH" (плюс любое число дополнительных описателей "H") представляет час в виде числа от 00 до 23, то есть час представлен в начинающемся с нуля 24-часовом формате, отсчитывающем часы с полуночи. Значение часа из одной цифры форматируется с начальным нулем. + + code + code + + date separator разделитель компонентов даты @@ -2492,6 +2512,11 @@ The "f" standard format specifier represents a combination of the long date ("D" Описатель стандартного формата "T" представляет строку пользовательского формата даты и времени, определяемую свойством DateTimeFormatInfo.LongTimePattern конкретных языка и региональных параметров. Например, строка пользовательского формата для инвариантных языка и региональных параметров имеет вид "HH:mm:ss". + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) минута (1–2 цифры) @@ -2944,11 +2969,6 @@ If the "g" format specifier is used without other custom format specifiers, it's Обновление комплексной инструкции с выражением Await помешает продолжению сеанса отладки. - - Changing visibility of a constructor will prevent the debug session from continuing. - Изменение видимости конструктора помешает продолжению сеанса отладки. - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. Запись переменной "{0}", которая не была записана прежде, помешает продолжению сеанса отладки. @@ -3155,8 +3175,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Deleting '{0}' will prevent the debug session from continuing. - Удаление "{0}" сделает продолжение сеанса отладки невозможным. + Deleting {0} will prevent the debug session from continuing. + Удаление "{0}" сделает продолжение сеанса отладки невозможным. @@ -3174,11 +3194,6 @@ If the "g" format specifier is used without other custom format specifiers, it's Удаление тела метода сделает продолжение сеанса отладки невозможным. - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - Активный оператор был удален из исходного метода. Чтобы продолжить выполнение сеанса отладки, необходимо отменить изменения или перезапустить сеанс. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Обновление модификатора async или iterator рядом с активным оператором сделает продолжение сеанса отладки невозможным. @@ -3209,11 +3224,6 @@ If the "g" format specifier is used without other custom format specifiers, it's Изменение инициализатора объекта "{0}" в универсальном типе сделает продолжение сеанса отладки невозможным. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - Изменение инициализатора объекта "{0}" в разделяемом типе сделает продолжение сеанса отладки невозможным. - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. При добавлении конструктора в тип с инициализатором поля или свойства, содержащим анонимную функцию, будет невозможно продолжить сеанс отладки. @@ -3265,8 +3275,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - Удаление "{0}", содержащего активный оператор, сделает продолжение сеанса отладки невозможным. + Removing {0} that contains an active statement will prevent the debug session from continuing. + Удаление "{0}", содержащего активный оператор, сделает продолжение сеанса отладки невозможным. @@ -3660,6 +3670,11 @@ When this standard format specifier is used, the formatting or parsing operation Когда используется этот описатель стандартного формата, операция форматирования или анализа всегда использует инвариантные язык и региональные параметры. + + static constructor + static constructor + + 'symbol' cannot be a namespace. '"символ" не может быть пространством имен. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 9bcba1c7c478a..c7b1accb0864d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -90,6 +90,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' demet öğesi adını ekle + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. Arabirim yöntemine '{0}' eklenmesi, hata ayıklama oturumunun devam etmesini engeller. @@ -125,6 +130,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Değişim koşulları yakalamak değil ve adlandırılamaz This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' Beklenen görev '{0}' döndürüyor @@ -205,6 +215,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' öğesini '{1}' olarak değiştirmek, durum makinesinin şeklini değiştirdiğinden hata ayıklama oturumunun devam etmesini engeller. + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style {0} kod stilini yapılandır @@ -2336,6 +2351,11 @@ If the "H" format specifier is used without other custom format specifiers, it's "HH" özel biçim belirticisi (ve herhangi bir sayıda ek "H" belirticisi) saati 00 ile 23 arasında bir sayı ile temsil eder; yani saat, gece yarısından bu yana geçen saat sayısını belirten sıfır tabanlı 24 saatlik bir düzen ile temsil edilir. Tek haneli bir saat, başına sıfır eklenerek biçimlendirilir. + + code + code + + date separator tarih ayırıcısı @@ -2492,6 +2512,11 @@ The "f" standard format specifier represents a combination of the long date ("D" "T" standart biçim belirticisi, belirli bir kültürün DateTimeFormatInfo.LongTimePattern özelliği tarafından tanımlanan bir özel tarih ve saat biçimi dizesini temsil eder. Örneğin, sabit kültür için özel biçim dizesi "HH:mm:ss" şeklindedir. + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) dakika (1-2 rakam) @@ -2944,11 +2969,6 @@ If the "g" format specifier is used without other custom format specifiers, it's Await ifadesi içeren bir karmaşık deyimin güncelleştirilmesi, hata ayıklama işleminin devam etmesini engeller. - - Changing visibility of a constructor will prevent the debug session from continuing. - Bir oluşturucunun görünürlüğünü değiştirmek, hata ayıklama oturumunun devam etmesini engeller. - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. Daha önce yakalanmayan '{0}' değişkeninin yakalanması hata ayıklama oturumunun devam etmesini engeller. @@ -3155,8 +3175,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Deleting '{0}' will prevent the debug session from continuing. - '{0}' öğesini silme, hata ayıklama oturumunun devam etmesini engelleyecek. + Deleting {0} will prevent the debug session from continuing. + '{0}' öğesini silme, hata ayıklama oturumunun devam etmesini engelleyecek. @@ -3174,11 +3194,6 @@ If the "g" format specifier is used without other custom format specifiers, it's Bir yöntem gövdesi silme, hata ayıklama oturumunun devam etmesini engelleyecek. - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - Etkin bir deyim özgün yönteminden kaldırılmış. Devam etmek için değişikliklerinizi geri almalısınız veya hata ayıklama oturumunu yeniden başlatmalısınız. - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. Etkin bir deyimin etrafındaki async ya da iterator değiştiricisini güncelleştirmek hata ayıklama oturumunun devam etmesini önler. @@ -3209,11 +3224,6 @@ If the "g" format specifier is used without other custom format specifiers, it's Genel bir tür içindeki '{0}' öğesinin başlatıcısını değiştirme, hata ayıklama oturumunun devam etmesini engelleyecek. - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - Kısmi bir tür içindeki '{0}' öğesinin başlatıcısını değiştirme, hata ayıklama oturumunun devam etmesini engelleyecek. - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. Anonim bir tür içeren alan veya özellik başlatıcısının bulunduğu bir türe oluşturucu eklenmesi, hata ayıklama oturumunun devam etmesini engelleyebilir. @@ -3265,8 +3275,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - Etkin bir deyim içeren '{0}' öğesini kaldırma, hata ayıklama oturumunun devam etmesini engelleyecek. + Removing {0} that contains an active statement will prevent the debug session from continuing. + Etkin bir deyim içeren '{0}' öğesini kaldırma, hata ayıklama oturumunun devam etmesini engelleyecek. @@ -3660,6 +3670,11 @@ When this standard format specifier is used, the formatting or parsing operation Bu standart biçim belirticisi kullanıldığında, biçimlendirme veya ayrıştırma işlemi her zaman sabit kültürü kullanır. + + static constructor + static constructor + + 'symbol' cannot be a namespace. 'symbol' bir ad alanı olamaz. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 8449cb520df6d..f2f4f83ed175e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -90,6 +90,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 添加元组元素名称 "{0}" + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. 将 "{0}" 添加进接口方法将阻止调试会话继续。 @@ -125,6 +130,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 替换条件不捕获且不能命名 This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' 等待任务返回“{0}” @@ -205,6 +215,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 将“{0}”更改为“{1}”会阻止调试会话继续执行,因为它会更改状态机的形状。 + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style 配置 {0} 代码样式 @@ -2336,6 +2351,11 @@ If the "H" format specifier is used without other custom format specifiers, it's "HH" 自定义格式说明符(另加任意数量的 "H" 说明符)将小时表示为从 00 至 23 的数字,即通过从零开始的 24 小时制表示小时,自午夜开始对小时计数。一位数字的小时数设置为带前导零的格式。 + + code + code + + date separator 日期分隔符 @@ -2492,6 +2512,11 @@ The "f" standard format specifier represents a combination of the long date ("D" "T" 标准格式说明符表示由特定区域性的 DateTimeFormatInfo.LongTimePattern 属性定义的自定义日期和时间格式字符串。例如,固定区域性的自定义格式字符串为 "HH:mm:ss"。 + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) 分钟(1-2 位数) @@ -2944,11 +2969,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 更新包含 Await 表达式的复杂语句将中止调试会话。 - - Changing visibility of a constructor will prevent the debug session from continuing. - 更改构造函数的可见性将会中止调试会话。 - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. 捕获之前尚未捕获过的变量“{0}”将阻止调试会话继续执行。 @@ -3155,8 +3175,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Deleting '{0}' will prevent the debug session from continuing. - 删除“{0}”将阻止调试会话继续。 + Deleting {0} will prevent the debug session from continuing. + 删除“{0}”将阻止调试会话继续。 @@ -3174,11 +3194,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 删除方法主体将阻止调试会话继续。 - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - 活动语句已从其初始方法中删除。必须还原更改才能继续或重新启动调试会话。 - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. 更新当前语句的 async 或 iterator 修饰符可以阻止调试会话继续。 @@ -3209,11 +3224,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 修改泛型类型中的“{0}”的初始值设定项将阻止调试会话继续。 - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - 修改分部类型中“{0}”的初始值设定项将阻止调试会话继续。 - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. 使用包含匿名类型的字段或属性初始值设定项向类型添加构造函数将阻止调试会话继续。 @@ -3265,8 +3275,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - 删除含活动语句的“{0}”将阻止调试会话继续。 + Removing {0} that contains an active statement will prevent the debug session from continuing. + 删除含活动语句的“{0}”将阻止调试会话继续。 @@ -3660,6 +3670,11 @@ When this standard format specifier is used, the formatting or parsing operation 使用此标准格式说明符时,格式设置或分析操作始终使用固定区域性。 + + static constructor + static constructor + + 'symbol' cannot be a namespace. '“symbol” 不能为命名空间。 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index eda7ed185b7c9..5e5bf32fe4c56 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -90,6 +90,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 新增元組元素名稱 ‘{0}’ + + Adding '{0}' into a generic type will prevent the debug session from continuing. + Adding '{0}' into a generic type will prevent the debug session from continuing. + + Adding '{0}' into an interface method will prevent the debug session from continuing. 將 '{0}' 新增至介面方法,會造成偵錯工作階段無法繼續。 @@ -125,6 +130,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 替代條件不會擷取,也無法命名 This is an error message shown to the user when they write an invalid Regular Expression. Example: (?(?'x')) + + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. + + Awaited task returns '{0}' 等待的工作會傳回 '{0}' @@ -205,6 +215,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 將 '{0}' 變更為 '{1}' 會變更狀態機器的圖形,進而使偵錯工作階段無法繼續。 + + Changing visibility of {0} will prevent the debug session from continuing. + Changing visibility of {0} will prevent the debug session from continuing. + + Configure {0} code style 設定 {0} 程式碼樣式 @@ -2336,6 +2351,11 @@ If the "H" format specifier is used without other custom format specifiers, it's "HH" 自訂格式規範 (加上任意數目的其他 "H" 規範) 代表小時,以數字 00 到 23 表示; 換句話說,小時即為自午夜起所經過的時數,是以零開始的 24 小時制。單一數字的小時格式,開頭不會出現零。 + + code + code + + date separator 日期分隔符號 @@ -2492,6 +2512,11 @@ The "f" standard format specifier represents a combination of the long date ("D" "T" 標準格式規範代表由特定文化特性的 DateTimeFormatInfo.LongTimePattern 屬性所定義的自訂日期與時間格式字串。例如,不因文化特性而異的自訂格式字串為 "HH:mm:ss"。 + + {0} '{1}' + {0} '{1}' + e.g. "method 'M'" + minute (1-2 digits) 分鐘 (1-2 位數) @@ -2944,11 +2969,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 更新包含 await 運算式的複雜陳述式會阻礙偵錯工作階段繼續進行。 - - Changing visibility of a constructor will prevent the debug session from continuing. - 變更建構函式的可見度會阻礙偵錯工作階段繼續進行。 - - Capturing variable '{0}' that hasn't been captured before will prevent the debug session from continuing. 擷取先前從未擷取過的變數 '{0}',會讓偵錯工作階段無法繼續。 @@ -3155,8 +3175,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Deleting '{0}' will prevent the debug session from continuing. - 刪除 '{0}' 會造成偵錯工作階段無法繼續。 + Deleting {0} will prevent the debug session from continuing. + 刪除 '{0}' 會造成偵錯工作階段無法繼續。 @@ -3174,11 +3194,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 刪除方法主體,會造成偵錯工作階段無法繼續。 - - An active statement has been removed from its original method. You must revert your changes to continue or restart the debugging session. - 現用陳述式已從其原始方法中移除。您必須還原您的變更才能繼續,或是重新啟動偵錯工作階段。 - - Updating async or iterator modifier around an active statement will prevent the debug session from continuing. 更新作用中陳述式前後的 async 或 iterator 修飾元,會造成偵錯工作階段無法繼續。 @@ -3209,11 +3224,6 @@ If the "g" format specifier is used without other custom format specifiers, it's 修改泛型類別中 '{0}' 的初始設定式,會造成偵錯工作階段無法繼續。 - - Modifying the initializer of '{0}' in a partial type will prevent the debug session from continuing. - 修改部分類型之 '{0}' 的初始設定式,會造成偵錯工作階段無法繼續。 - - Adding a constructor to a type with a field or property initializer that contains an anonymous function will prevent the debug session from continuing. 將建構函式加入類型,並附上包含會讓偵錯工作階段無法繼續的匿名函式欄位或屬性初始設定式。 @@ -3265,8 +3275,8 @@ If the "g" format specifier is used without other custom format specifiers, it's - Removing '{0}' that contains an active statement will prevent the debug session from continuing. - 移除包含現用陳述式的 '{0}',會造成偵錯工作階段無法繼續。 + Removing {0} that contains an active statement will prevent the debug session from continuing. + 移除包含現用陳述式的 '{0}',會造成偵錯工作階段無法繼續。 @@ -3660,6 +3670,11 @@ When this standard format specifier is used, the formatting or parsing operation 使用此標準格式規範時,格式化或剖析作業永遠不因文化特性而異。 + + static constructor + static constructor + + 'symbol' cannot be a namespace. 'symbol' 不可為命名空間。 diff --git a/src/Features/LanguageServer/Protocol/CustomProtocol/RoslynCompletionItem.cs b/src/Features/LanguageServer/Protocol/CustomProtocol/RoslynCompletionItem.cs deleted file mode 100644 index c88b29d239422..0000000000000 --- a/src/Features/LanguageServer/Protocol/CustomProtocol/RoslynCompletionItem.cs +++ /dev/null @@ -1,49 +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.Runtime.Serialization; -using Microsoft.VisualStudio.LanguageServer.Protocol; - -namespace Microsoft.CodeAnalysis.LanguageServer.CustomProtocol -{ - // TODO - This should be deleted when liveshare moves to VSCompletionItem. - // https://github.com/dotnet/roslyn/projects/45#card-21249665 - [DataContract] - internal class RoslynCompletionItem : CompletionItem - { - /// - /// A set of custom tags on a completion item. Roslyn has information here to get icons. - /// - [DataMember(Name = "tags")] - public string[] Tags { get; set; } - - /// - /// The description for a completion item. - /// - [DataMember(Name = "description")] - public RoslynTaggedText[] Description { get; set; } - - public static RoslynCompletionItem From(CompletionItem completionItem) - { - return new RoslynCompletionItem - { - AdditionalTextEdits = completionItem.AdditionalTextEdits, - Command = completionItem.Command, - CommitCharacters = completionItem.CommitCharacters, - Data = completionItem.Data, - Detail = completionItem.Detail, - Documentation = completionItem.Documentation, - FilterText = completionItem.FilterText, - InsertText = completionItem.InsertText, - InsertTextFormat = completionItem.InsertTextFormat, - Kind = completionItem.Kind, - Label = completionItem.Label, - SortText = completionItem.SortText, - TextEdit = completionItem.TextEdit - }; - } - } -} diff --git a/src/Features/LanguageServer/Protocol/CustomProtocol/RoslynTaggedText.cs b/src/Features/LanguageServer/Protocol/CustomProtocol/RoslynTaggedText.cs deleted file mode 100644 index ad6e3bd1b2e85..0000000000000 --- a/src/Features/LanguageServer/Protocol/CustomProtocol/RoslynTaggedText.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. - -#nullable disable - -namespace Microsoft.CodeAnalysis.LanguageServer.CustomProtocol -{ - // TODO - This should be deleted when liveshare moves to VSCompletionItem. - // https://github.com/dotnet/roslyn/projects/45#card-21249665 - internal class RoslynTaggedText - { - public string Tag { get; set; } - public string Text { get; set; } - - public TaggedText ToTaggedText() => new TaggedText(Tag, Text); - } -} diff --git a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs index 1594ccb4745ff..45b936acf61c1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionHandler.cs @@ -6,14 +6,17 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Experiments; using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Text.Adornments; using Roslyn.Utilities; using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; @@ -78,22 +81,59 @@ public CompletionHandler( var completionTrigger = await ProtocolConversions.LSPToRoslynCompletionTriggerAsync(request.Context, document, position, cancellationToken).ConfigureAwait(false); var list = await completionService.GetCompletionsAsync(document, position, completionTrigger, options: completionOptions, cancellationToken: cancellationToken).ConfigureAwait(false); - if (list == null) + if (list == null || list.Items.IsEmpty) { return null; } - var lspVSClientCapability = context.ClientCapabilities?.HasVisualStudioLspCapability() == true; - + var lspVSClientCapability = context.ClientCapabilities.HasVisualStudioLspCapability() == true; + var snippetsSupported = context.ClientCapabilities.TextDocument?.Completion?.CompletionItem?.SnippetSupport ?? false; var commitCharactersRuleCache = new Dictionary, ImmutableArray>(); // Cache the completion list so we can avoid recomputation in the resolve handler var resultId = await _completionListCache.UpdateCacheAsync(list, cancellationToken).ConfigureAwait(false); + // Feature flag to enable the return of TextEdits instead of InsertTexts (will increase payload size). + // Flag is defined in VisualStudio\Core\Def\PackageRegistration.pkgdef. + // We also check against the CompletionOption for test purposes only. + Contract.ThrowIfNull(context.Solution); + var featureFlagService = context.Solution.Workspace.Services.GetRequiredService(); + var returnTextEdits = featureFlagService.IsExperimentEnabled(WellKnownExperimentNames.LSPCompletion) || + completionOptions.GetOption(CompletionOptions.ForceRoslynLSPCompletionExperiment, document.Project.Language); + + SourceText? documentText = null; + TextSpan? defaultSpan = null; + LSP.Range? defaultRange = null; + if (returnTextEdits) + { + // We want to compute the document's text just once. + documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + + // We use the first item in the completion list as our comparison point for span + // and range for optimization when generating the TextEdits later on. + var completionChange = await completionService.GetChangeAsync( + document, list.Items.First(), cancellationToken: cancellationToken).ConfigureAwait(false); + + // If possible, we want to compute the item's span and range just once. + // Individual items can override this range later. + defaultSpan = completionChange.TextChange.Span; + defaultRange = ProtocolConversions.TextSpanToRange(defaultSpan.Value, documentText); + } + + var stringBuilder = new StringBuilder(); + using var _ = ArrayBuilder.GetInstance(out var lspCompletionItems); + foreach (var item in list.Items) + { + var lspCompletionItem = await CreateLSPCompletionItemAsync( + request, document, item, resultId, lspVSClientCapability, completionTrigger, commitCharactersRuleCache, + completionService, context.ClientName, returnTextEdits, snippetsSupported, stringBuilder, documentText, + defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); + lspCompletionItems.Add(lspCompletionItem); + } + return new LSP.VSCompletionList { - Items = list.Items.Select(item => CreateLSPCompletionItem( - request, item, resultId, lspVSClientCapability, completionTrigger, commitCharactersRuleCache)).ToArray(), + Items = lspCompletionItems.ToArray(), SuggestionMode = list.SuggestionModeItem != null, }; @@ -114,39 +154,77 @@ bool IsValidTriggerCharacterForDocument(Document document, char triggerCharacter return true; } - static LSP.CompletionItem CreateLSPCompletionItem( + static async Task CreateLSPCompletionItemAsync( LSP.CompletionParams request, + Document document, CompletionItem item, long? resultId, bool useVSCompletionItem, CompletionTrigger completionTrigger, - Dictionary, ImmutableArray> commitCharacterRulesCache) + Dictionary, ImmutableArray> commitCharacterRulesCache, + CompletionService completionService, + string? clientName, + bool returnTextEdits, + bool snippetsSupported, + StringBuilder stringBuilder, + SourceText? documentText, + TextSpan? defaultSpan, + LSP.Range? defaultRange, + CancellationToken cancellationToken) { if (useVSCompletionItem) { - var vsCompletionItem = CreateCompletionItem(request, item, resultId, completionTrigger, commitCharacterRulesCache); + var vsCompletionItem = await CreateCompletionItemAsync( + request, document, item, resultId, completionTrigger, commitCharacterRulesCache, + completionService, clientName, returnTextEdits, snippetsSupported, stringBuilder, + documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); vsCompletionItem.Icon = new ImageElement(item.Tags.GetFirstGlyph().GetImageId()); return vsCompletionItem; } else { - var roslynCompletionItem = CreateCompletionItem(request, item, resultId, completionTrigger, commitCharacterRulesCache); + var roslynCompletionItem = await CreateCompletionItemAsync( + request, document, item, resultId, completionTrigger, commitCharacterRulesCache, + completionService, clientName, returnTextEdits, snippetsSupported, stringBuilder, + documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); return roslynCompletionItem; } } - static TCompletionItem CreateCompletionItem( + static async Task CreateCompletionItemAsync( LSP.CompletionParams request, + Document document, CompletionItem item, long? resultId, CompletionTrigger completionTrigger, - Dictionary, ImmutableArray> commitCharacterRulesCache) where TCompletionItem : LSP.CompletionItem, new() + Dictionary, ImmutableArray> commitCharacterRulesCache, + CompletionService completionService, + string? clientName, + bool returnTextEdits, + bool snippetsSupported, + StringBuilder stringBuilder, + SourceText? documentText, + TextSpan? defaultSpan, + LSP.Range? defaultRange, + CancellationToken cancellationToken) where TCompletionItem : LSP.CompletionItem, new() { - var completeDisplayText = item.DisplayTextPrefix + item.DisplayText + item.DisplayTextSuffix; + // Generate display text + stringBuilder.Append(item.DisplayTextPrefix); + stringBuilder.Append(item.DisplayText); + stringBuilder.Append(item.DisplayTextSuffix); + var completeDisplayText = stringBuilder.ToString(); + stringBuilder.Clear(); + + // The TextEdits for override and partial method completions are provided in the resolve handler. + // We do not provide them in this handler. + // HACK: We should not be accessing the completion item's properties directly. + // See https://github.com/dotnet/roslyn/issues/51396. + item.Properties.TryGetValue("Modifiers", out var modifier); + var isOverrideOrPartialMethodCompletion = modifier != null && (modifier.Contains("Override") || modifier.Contains("Partial")); + var completionItem = new TCompletionItem { Label = completeDisplayText, - InsertText = item.Properties.ContainsKey("InsertionText") ? item.Properties["InsertionText"] : completeDisplayText, SortText = item.SortText, FilterText = item.FilterText, Kind = GetCompletionKind(item.Tags), @@ -160,6 +238,31 @@ static TCompletionItem CreateCompletionItem( }, Preselect = item.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection, }; + + // Override and partial method completions are always populated in the resolve handler, so we + // leave both TextEdit and InsertText unpopulated in these cases. + if (isOverrideOrPartialMethodCompletion) + { + // Razor C# is currently the only language client that supports LSP.InsertTextFormat.Snippet. + // We can enable it for regular C# once LSP is used for local completion. + if (snippetsSupported && clientName == ProtocolConstants.RazorCSharp) + { + completionItem.InsertTextFormat = LSP.InsertTextFormat.Snippet; + } + } + // If the feature flag is on, always return a TextEdit. + else if (returnTextEdits) + { + var textEdit = await GenerateTextEdit( + document, item, completionService, documentText, defaultSpan, defaultRange, cancellationToken).ConfigureAwait(false); + completionItem.TextEdit = textEdit; + } + // If the feature flag is off, return an InsertText. + else + { + completionItem.InsertText = item.Properties.ContainsKey("InsertionText") ? item.Properties["InsertionText"] : completeDisplayText; + } + var commitCharacters = GetCommitCharacters(item, commitCharacterRulesCache); if (commitCharacters != null) { @@ -167,6 +270,34 @@ static TCompletionItem CreateCompletionItem( } return completionItem; + + static async Task GenerateTextEdit( + Document document, + CompletionItem item, + CompletionService completionService, + SourceText? documentText, + TextSpan? defaultSpan, + LSP.Range? defaultRange, + CancellationToken cancellationToken) + { + Contract.ThrowIfNull(documentText); + Contract.ThrowIfNull(defaultSpan); + Contract.ThrowIfNull(defaultRange); + + var completionChange = await completionService.GetChangeAsync( + document, item, cancellationToken: cancellationToken).ConfigureAwait(false); + var completionChangeSpan = completionChange.TextChange.Span; + + var textEdit = new LSP.TextEdit() + { + NewText = completionChange.TextChange.NewText ?? "", + Range = completionChangeSpan == defaultSpan.Value + ? defaultRange + : ProtocolConversions.TextSpanToRange(completionChangeSpan, documentText), + }; + + return textEdit; + } } static string[]? GetCommitCharacters(CompletionItem item, Dictionary, ImmutableArray> currentRuleCache) diff --git a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionResolveHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionResolveHandler.cs index 081b7d5bcc2a1..07332e1c2cedf 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionResolveHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Completion/CompletionResolveHandler.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.LanguageServer.CustomProtocol; using Microsoft.CodeAnalysis.LanguageServer.Handler.Completion; using Microsoft.VisualStudio.Text.Adornments; using Newtonsoft.Json.Linq; @@ -74,7 +73,12 @@ private static CompletionResolveData GetCompletionResolveData(LSP.CompletionItem } } - var selectedItem = list.Items.FirstOrDefault(i => i.DisplayText == data.DisplayText); + // Find the matching completion item in the completion list + var selectedItem = list.Items.FirstOrDefault( + i => data.DisplayText == i.DisplayText && + completionItem.Label.StartsWith(i.DisplayTextPrefix) && + completionItem.Label.EndsWith(i.DisplayTextSuffix)); + if (selectedItem == null) { return completionItem; @@ -82,45 +86,73 @@ private static CompletionResolveData GetCompletionResolveData(LSP.CompletionItem var description = await completionService.GetDescriptionAsync(document, selectedItem, cancellationToken).ConfigureAwait(false); - var lspVSClientCapability = context.ClientCapabilities?.HasVisualStudioLspCapability() == true; - LSP.CompletionItem resolvedCompletionItem; - if (lspVSClientCapability) + if (completionItem is LSP.VSCompletionItem vsCompletionItem) { - resolvedCompletionItem = CloneVSCompletionItem(completionItem); - ((LSP.VSCompletionItem)resolvedCompletionItem).Description = new ClassifiedTextElement(description.TaggedParts + vsCompletionItem.Description = new ClassifiedTextElement(description.TaggedParts .Select(tp => new ClassifiedTextRun(tp.Tag.ToClassificationTypeName(), tp.Text))); } - else + + // We compute the TextEdit resolves for override and partial method completions here. + // Lazily resolving TextEdits is technically a violation of the LSP spec, but is + // currently supported by the VS client anyway. Once the VS client adheres to the spec, + // this logic will need to change and VS will need to provide official support for + // TextEdit resolution in some form. + if (completionItem.InsertText == null && completionItem.TextEdit == null) { - resolvedCompletionItem = RoslynCompletionItem.From(completionItem); - ((RoslynCompletionItem)resolvedCompletionItem).Description = description.TaggedParts.Select( - tp => new RoslynTaggedText { Tag = tp.Tag, Text = tp.Text }).ToArray(); + var snippetsSupported = context.ClientCapabilities.TextDocument?.Completion?.CompletionItem?.SnippetSupport ?? false; + + completionItem.TextEdit = await GenerateTextEditAsync( + document, completionService, selectedItem, snippetsSupported, cancellationToken).ConfigureAwait(false); } - resolvedCompletionItem.Detail = description.TaggedParts.GetFullText(); - return resolvedCompletionItem; + completionItem.Detail = description.TaggedParts.GetFullText(); + return completionItem; } - private static LSP.VSCompletionItem CloneVSCompletionItem(LSP.CompletionItem completionItem) + // Internal for testing + internal static async Task GenerateTextEditAsync( + Document document, + CompletionService completionService, + CompletionItem selectedItem, + bool snippetsSupported, + CancellationToken cancellationToken) { - return new LSP.VSCompletionItem + var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + + var completionChange = await completionService.GetChangeAsync( + document, selectedItem, cancellationToken: cancellationToken).ConfigureAwait(false); + var completionChangeSpan = completionChange.TextChange.Span; + var newText = completionChange.TextChange.NewText; + Contract.ThrowIfNull(newText); + + // If snippets are supported, that means we can move the caret (represented by $0) to + // a new location. + if (snippetsSupported) { - AdditionalTextEdits = completionItem.AdditionalTextEdits, - Command = completionItem.Command, - CommitCharacters = completionItem.CommitCharacters, - Data = completionItem.Data, - Detail = completionItem.Detail, - Documentation = completionItem.Documentation, - FilterText = completionItem.FilterText, - Icon = completionItem is LSP.VSCompletionItem vsCompletionItem ? vsCompletionItem.Icon : null, - InsertText = completionItem.InsertText, - InsertTextFormat = completionItem.InsertTextFormat, - Kind = completionItem.Kind, - Label = completionItem.Label, - SortText = completionItem.SortText, - TextEdit = completionItem.TextEdit, - Preselect = completionItem.Preselect + var caretPosition = completionChange.NewPosition; + if (caretPosition.HasValue) + { + // caretPosition is the absolute position of the caret in the document. + // We want the position relative to the start of the snippet. + var relativeCaretPosition = caretPosition.Value - completionChangeSpan.Start; + + // The caret could technically be placed outside the bounds of the text + // being inserted. This situation is currently unsupported in LSP, so in + // these cases we won't move the caret. + if (relativeCaretPosition >= 0 && relativeCaretPosition <= newText.Length) + { + newText = newText.Insert(relativeCaretPosition, "$0"); + } + } + } + + var textEdit = new LSP.TextEdit() + { + NewText = newText, + Range = ProtocolConversions.TextSpanToRange(completionChangeSpan, documentText), }; + + return textEdit; } } } diff --git a/src/Features/LanguageServer/Protocol/ProtocolConstants.cs b/src/Features/LanguageServer/Protocol/ProtocolConstants.cs new file mode 100644 index 0000000000000..89819124500af --- /dev/null +++ b/src/Features/LanguageServer/Protocol/ProtocolConstants.cs @@ -0,0 +1,11 @@ +// 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.LanguageServer +{ + internal class ProtocolConstants + { + public const string RazorCSharp = "RazorCSharp"; + } +} diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs index 1f83ad577baa4..58fdf5c574f2a 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionResolveTests.cs @@ -4,10 +4,14 @@ #nullable disable +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.Text.Adornments; using Roslyn.Test.Utilities; @@ -31,20 +35,137 @@ void M() }"; using var testLspServer = CreateTestLspServer(markup, out var locations); var tags = new string[] { "Class", "Internal" }; - var completionParams = CreateCompletionParams(locations["caret"].Single(), LSP.VSCompletionInvokeKind.Explicit, "\0", LSP.CompletionTriggerKind.Invoked); + var completionParams = CreateCompletionParams( + locations["caret"].Single(), LSP.VSCompletionInvokeKind.Explicit, "\0", LSP.CompletionTriggerKind.Invoked); var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var completionItem = await CreateCompletionItemAsync( - "A", LSP.CompletionItemKind.Class, tags, completionParams, document, commitCharacters: CompletionRules.Default.DefaultCommitCharacters).ConfigureAwait(false); + label: "A", LSP.CompletionItemKind.Class, tags, completionParams, document, + commitCharacters: CompletionRules.Default.DefaultCommitCharacters, insertText: "A").ConfigureAwait(false); var description = new ClassifiedTextElement(CreateClassifiedTextRunForClass("A")); var clientCapabilities = new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; var expected = CreateResolvedCompletionItem(completionItem, description, "class A", null); - var results = (LSP.VSCompletionItem)await RunResolveCompletionItemAsync(testLspServer, completionItem, clientCapabilities).ConfigureAwait(false); + var results = (LSP.VSCompletionItem)await RunResolveCompletionItemAsync( + testLspServer, completionItem, clientCapabilities).ConfigureAwait(false); AssertJsonEquals(expected, results); } + [Fact] + public async Task TestResolveOverridesCompletionItemAsync() + { + var markup = +@"abstract class A +{ + public abstract void M(); +} + +class B : A +{ + override {|caret:|} +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var tags = new string[] { "Method", "Public" }; + var completionParams = CreateCompletionParams( + locations["caret"].Single(), LSP.VSCompletionInvokeKind.Explicit, "\0", LSP.CompletionTriggerKind.Invoked); + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + + var completionItem = await CreateCompletionItemAsync( + label: "M()", LSP.CompletionItemKind.Method, tags, completionParams, document, + commitCharacters: CompletionRules.Default.DefaultCommitCharacters).ConfigureAwait(false); + var clientCapabilities = new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; + + var results = (LSP.VSCompletionItem)await RunResolveCompletionItemAsync( + testLspServer, completionItem, clientCapabilities).ConfigureAwait(false); + + Assert.NotNull(results.TextEdit); + Assert.Null(results.InsertText); + Assert.Equal(@"public override void M() + { + throw new System.NotImplementedException(); + }", results.TextEdit.NewText); + } + + [Fact] + public async Task TestResolveOverridesCompletionItem_SnippetsEnabledAsync() + { + var markup = +@"abstract class A +{ + public abstract void M(); +} + +class B : A +{ + override {|caret:|} +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var tags = new string[] { "Method", "Public" }; + var completionParams = CreateCompletionParams( + locations["caret"].Single(), LSP.VSCompletionInvokeKind.Explicit, "\0", LSP.CompletionTriggerKind.Invoked); + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + + var completionItem = await CreateCompletionItemAsync( + label: "M()", LSP.CompletionItemKind.Method, tags, completionParams, document, + commitCharacters: CompletionRules.Default.DefaultCommitCharacters).ConfigureAwait(false); + + // Explicitly enable snippets. This allows us to set the cursor with $0. Currently only applies to C# in Razor docs. + var clientCapabilities = new LSP.VSClientCapabilities + { + SupportsVisualStudioExtensions = true, + TextDocument = new LSP.TextDocumentClientCapabilities + { + Completion = new CompletionSetting + { + CompletionItem = new CompletionItemSetting + { + SnippetSupport = true + } + } + } + }; + + var results = (LSP.VSCompletionItem)await RunResolveCompletionItemAsync( + testLspServer, completionItem, clientCapabilities).ConfigureAwait(false); + + Assert.NotNull(results.TextEdit); + Assert.Null(results.InsertText); + Assert.Equal(@"public override void M() + { + throw new System.NotImplementedException();$0 + }", results.TextEdit.NewText); + } + + [Fact] + public async Task TestResolveOverridesCompletionItem_SnippetsEnabled_CaretOutOfSnippetScopeAsync() + { + var markup = +@"abstract class A +{ + public abstract void M(); +} + +class B : A +{ + override {|caret:|} +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var tags = new string[] { "Method", "Public" }; + var completionParams = CreateCompletionParams( + locations["caret"].Single(), LSP.VSCompletionInvokeKind.Explicit, "\0", LSP.CompletionTriggerKind.Invoked); + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + + var selectedItem = CodeAnalysis.Completion.CompletionItem.Create(displayText: "M"); + var textEdit = await CompletionResolveHandler.GenerateTextEditAsync( + document, new TestCaretOutOfScopeCompletionService(), selectedItem, snippetsSupported: true, CancellationToken.None).ConfigureAwait(false); + + Assert.Equal(@"public override void M() + { + throw new System.NotImplementedException(); + }", textEdit.NewText); + } + private static async Task RunResolveCompletionItemAsync(TestLspServer testLspServer, LSP.CompletionItem completionItem, LSP.ClientCapabilities clientCapabilities = null) { return await testLspServer.ExecuteRequestAsync(LSP.Methods.TextDocumentCompletionResolveName, @@ -78,5 +199,33 @@ private static ClassifiedTextRun[] CreateClassifiedTextRunForClass(string classN new ClassifiedTextRun("whitespace", " "), new ClassifiedTextRun("class name", className) }; + + private class TestCaretOutOfScopeCompletionService : CompletionService + { + public override string Language => LanguageNames.CSharp; + + public override Task GetCompletionsAsync( + Document document, + int caretPosition, + CompletionTrigger trigger = default, + ImmutableHashSet roles = null, + OptionSet options = null, + CancellationToken cancellationToken = default) + => Task.FromResult(CodeAnalysis.Completion.CompletionList.Empty); + + public override Task GetChangeAsync( + Document document, + CodeAnalysis.Completion.CompletionItem item, + char? commitCharacter = null, + CancellationToken cancellationToken = default) + { + var textChange = new TextChange(span: new TextSpan(start: 77, length: 9), newText: @"public override void M() + { + throw new System.NotImplementedException(); + }"); + + return Task.FromResult(CompletionChange.Create(textChange, newPosition: 0)); + } + } } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs index 99e8c3d6ab9b7..ef84875a52058 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Completion/CompletionTests.cs @@ -39,8 +39,8 @@ void M() var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); - var expected = await CreateCompletionItemAsync("A", LSP.CompletionItemKind.Class, new string[] { "Class", "Internal" }, - completionParams, document, commitCharacters: CompletionRules.Default.DefaultCommitCharacters).ConfigureAwait(false); + var expected = await CreateCompletionItemAsync(label: "A", LSP.CompletionItemKind.Class, new string[] { "Class", "Internal" }, + completionParams, document, commitCharacters: CompletionRules.Default.DefaultCommitCharacters, insertText: "A").ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); @@ -61,7 +61,7 @@ void M() var solution = testLspServer.TestWorkspace.CurrentSolution; // Make sure the unimported types option is on by default. - solution = solution.WithOptions(solution.Options + testLspServer.TestWorkspace.SetOptions(testLspServer.TestWorkspace.CurrentSolution.Options .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.CSharp, true) .WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, true)); @@ -119,7 +119,8 @@ void M() var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync("A", LSP.CompletionItemKind.Class, new string[] { "Class", "Internal" }, - completionParams, document, preselect: true, commitCharacters: ImmutableArray.Create(' ', '(', '[', '{', ';', '.')).ConfigureAwait(false); + completionParams, document, preselect: true, commitCharacters: ImmutableArray.Create(' ', '(', '[', '{', ';', '.'), + insertText: "A").ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); @@ -177,13 +178,14 @@ void M() var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); var expected = await CreateCompletionItemAsync( - "d", LSP.CompletionItemKind.Text, new string[] { "Text" }, completionParams, document, sortText: "0000").ConfigureAwait(false); + label: "d", LSP.CompletionItemKind.Text, new string[] { "Text" }, completionParams, document, insertText: "d", sortText: "0000").ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); } [Fact] + [WorkItem(50964, "https://github.com/dotnet/roslyn/issues/50964")] public async Task TestGetRegexCompletionsAsync() { var markup = @@ -192,28 +194,73 @@ class A { void M() { - new Regex(""\\{|caret:|}""); + new Regex(""{|caret:|}""); } }"; using var testLspServer = CreateTestLspServer(markup, out var locations); var completionParams = CreateCompletionParams( locations["caret"].Single(), - invokeKind: LSP.VSCompletionInvokeKind.Typing, + invokeKind: LSP.VSCompletionInvokeKind.Explicit, + triggerCharacter: "\0", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var solution = testLspServer.GetCurrentSolution(); + var document = solution.Projects.First().Documents.First(); + + // Set to use prototype completion behavior (i.e. feature flag). + var options = solution.Workspace.Options.WithChangedOption(CompletionOptions.ForceRoslynLSPCompletionExperiment, LanguageNames.CSharp, true); + Assert.True(solution.Workspace.TryApplyChanges(solution.WithOptions(options))); + + var textEdit = GenerateTextEdit(@"\\A", startLine: 5, startChar: 19, endLine: 5, endChar: 19); + + var expected = await CreateCompletionItemAsync( + label: @"\A", LSP.CompletionItemKind.Text, new string[] { "Text" }, completionParams, document, textEdit: textEdit, + sortText: "0000").ConfigureAwait(false); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + AssertJsonEquals(expected, results.Items.First()); + } + + [Fact] + [WorkItem(50964, "https://github.com/dotnet/roslyn/issues/50964")] + public async Task TestGetRegexLiteralCompletionsAsync() + { + var markup = +@"using System.Text.RegularExpressions; +class A +{ + void M() + { + new Regex(@""\{|caret:|}""); + } +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Explicit, triggerCharacter: "\\", triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); - var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + var solution = testLspServer.GetCurrentSolution(); + var document = solution.Projects.First().Documents.First(); + + // Set to use prototype completion behavior (i.e. feature flag). + var options = solution.Workspace.Options.WithChangedOption(CompletionOptions.ForceRoslynLSPCompletionExperiment, LanguageNames.CSharp, true); + Assert.True(solution.Workspace.TryApplyChanges(solution.WithOptions(options))); + + var textEdit = GenerateTextEdit(@"\A", startLine: 5, startChar: 20, endLine: 5, endChar: 21); var expected = await CreateCompletionItemAsync( - "\\A", LSP.CompletionItemKind.Text, new string[] { "Text" }, completionParams, document, sortText: "0000").ConfigureAwait(false); + label: @"\A", LSP.CompletionItemKind.Text, new string[] { "Text" }, completionParams, document, textEdit: textEdit, + sortText: "0000").ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); } [Fact] - [WorkItem(47743, "https://github.com/dotnet/roslyn/issues/47743")] - public async Task TestGetRegexCompletionsTargetTypedAsync() + [WorkItem(50964, "https://github.com/dotnet/roslyn/issues/50964")] + public async Task TestGetRegexCompletionsReplaceTextAsync() { var markup = @"using System.Text.RegularExpressions; @@ -231,10 +278,18 @@ void M() triggerCharacter: "\\", triggerKind: LSP.CompletionTriggerKind.TriggerCharacter); - var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + var solution = testLspServer.GetCurrentSolution(); + var document = solution.Projects.First().Documents.First(); + + // Set to use prototype completion behavior (i.e. feature flag). + var options = solution.Workspace.Options.WithChangedOption(CompletionOptions.ForceRoslynLSPCompletionExperiment, LanguageNames.CSharp, true); + Assert.True(solution.Workspace.TryApplyChanges(solution.WithOptions(options))); + + var textEdit = GenerateTextEdit(@"\\A", startLine: 5, startChar: 23, endLine: 5, endChar: 25); var expected = await CreateCompletionItemAsync( - "\\A", LSP.CompletionItemKind.Text, new string[] { "Text" }, completionParams, document, sortText: "0000").ConfigureAwait(false); + label: @"\A", LSP.CompletionItemKind.Text, new string[] { "Text" }, completionParams, document, textEdit: textEdit, + sortText: "0000").ConfigureAwait(false); var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); AssertJsonEquals(expected, results.Items.First()); @@ -333,6 +388,60 @@ void M() Assert.Null(results); } + [Fact] + public async Task TestDoNotProvideOverrideTextEditsOrInsertTextAsync() + { + var markup = +@"abstract class A +{ + public abstract void M(); +} + +class B : A +{ + override {|caret:|} +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Explicit, + triggerCharacter: "\0", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Null(results.Items.First().TextEdit); + Assert.Null(results.Items.First().InsertText); + } + + [Fact] + public async Task TestDoNotProvidePartialMethodTextEditsOrInsertTextAsync() + { + var markup = +@"partial class C +{ + partial void Method(); +} + +partial class C +{ + partial {|caret:|} +}"; + using var testLspServer = CreateTestLspServer(markup, out var locations); + var completionParams = CreateCompletionParams( + locations["caret"].Single(), + invokeKind: LSP.VSCompletionInvokeKind.Explicit, + triggerCharacter: "\0", + triggerKind: LSP.CompletionTriggerKind.Invoked); + + var document = testLspServer.GetCurrentSolution().Projects.First().Documents.First(); + + var results = await RunGetCompletionsAsync(testLspServer, completionParams).ConfigureAwait(false); + Assert.Null(results.Items.First().TextEdit); + Assert.Null(results.Items.First().InsertText); + } + private static async Task RunGetCompletionsAsync(TestLspServer testLspServer, LSP.CompletionParams completionParams) { var clientCapabilities = new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true }; diff --git a/src/Features/Lsif/Generator/Writing/BatchingLsifJsonWriter.cs b/src/Features/Lsif/Generator/Writing/BatchingLsifJsonWriter.cs index ff7a6b6f62ba6..0968805ed32f1 100644 --- a/src/Features/Lsif/Generator/Writing/BatchingLsifJsonWriter.cs +++ b/src/Features/Lsif/Generator/Writing/BatchingLsifJsonWriter.cs @@ -57,6 +57,14 @@ public void Write(Element element) } } + public void WriteAll(List elements) + { + lock (_elementsGate) + { + _elements.AddRange(elements); + } + } + public void FlushToUnderlyingAndEmpty() { lock (_writingGate) @@ -69,10 +77,7 @@ public void FlushToUnderlyingAndEmpty() _elements = new List(); } - foreach (var element in localElements) - { - _underlyingWriter.Write(element); - } + _underlyingWriter.WriteAll(localElements); } } } diff --git a/src/Features/Lsif/Generator/Writing/ILsifJsonWriter.cs b/src/Features/Lsif/Generator/Writing/ILsifJsonWriter.cs index 0020828d1041a..624453c157b96 100644 --- a/src/Features/Lsif/Generator/Writing/ILsifJsonWriter.cs +++ b/src/Features/Lsif/Generator/Writing/ILsifJsonWriter.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.Collections.Generic; using Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Graph; namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Writing @@ -9,5 +10,6 @@ namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Writing internal interface ILsifJsonWriter { void Write(Element element); + void WriteAll(List elements); } } diff --git a/src/Features/Lsif/Generator/Writing/JsonModeLsifJsonWriter.cs b/src/Features/Lsif/Generator/Writing/JsonModeLsifJsonWriter.cs index d0be03a7bfdb1..08fc997545a03 100644 --- a/src/Features/Lsif/Generator/Writing/JsonModeLsifJsonWriter.cs +++ b/src/Features/Lsif/Generator/Writing/JsonModeLsifJsonWriter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.IO; using Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Graph; using Newtonsoft.Json; @@ -44,6 +45,15 @@ public void Write(Element element) } } + public void WriteAll(List elements) + { + lock (_writeGate) + { + foreach (var element in elements) + _jsonSerializer.Serialize(_jsonTextWriter, element); + } + } + public void Dispose() { _jsonTextWriter.WriteWhitespace(Environment.NewLine); diff --git a/src/Features/Lsif/Generator/Writing/LineModeLsifJsonWriter.cs b/src/Features/Lsif/Generator/Writing/LineModeLsifJsonWriter.cs index b4090b9093e5c..6a863cabac99f 100644 --- a/src/Features/Lsif/Generator/Writing/LineModeLsifJsonWriter.cs +++ b/src/Features/Lsif/Generator/Writing/LineModeLsifJsonWriter.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.Collections.Generic; using System.IO; using Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Graph; using Newtonsoft.Json; @@ -40,5 +41,18 @@ public void Write(Element element) _outputWriter.WriteLine(line); } } + + public void WriteAll(List elements) + { + var lines = new List(); + foreach (var element in elements) + lines.Add(JsonConvert.SerializeObject(element, _settings)); + + lock (_writeGate) + { + foreach (var line in lines) + _outputWriter.WriteLine(line); + } + } } } diff --git a/src/Features/Lsif/GeneratorTest/Utilities/TestLsifJsonWriter.vb b/src/Features/Lsif/GeneratorTest/Utilities/TestLsifJsonWriter.vb index b89436583432b..ae3470f1f2e83 100644 --- a/src/Features/Lsif/GeneratorTest/Utilities/TestLsifJsonWriter.vb +++ b/src/Features/Lsif/GeneratorTest/Utilities/TestLsifJsonWriter.vb @@ -18,6 +18,12 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.U Private ReadOnly _elementsById As Dictionary(Of Id(Of Element), Element) = New Dictionary(Of Id(Of Element), Element) Private ReadOnly _edgesByOutVertex As Dictionary(Of Vertex, List(Of Edge)) = New Dictionary(Of Vertex, List(Of Edge)) + Private Sub ILsifJsonWriter_WriteAll(elements As List(Of Element)) Implements ILsifJsonWriter.WriteAll + For Each element In elements + ILsifJsonWriter_Write(element) + Next + End Sub + Private Sub ILsifJsonWriter_Write(element As Element) Implements ILsifJsonWriter.Write SyncLock _gate ' We intentionally use Add so it'll throw if we have a duplicate ID. diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ExtensionMethodImportCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ExtensionMethodImportCompletionProvider.vb index 532159840d425..ff0e435bcafcc 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ExtensionMethodImportCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ExtensionMethodImportCompletionProvider.vb @@ -37,8 +37,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Public Overrides ReadOnly Property TriggerCharacters As ImmutableHashSet(Of Char) = CompletionUtilities.CommonTriggerCharsAndParen - Protected Overrides Function CreateContextAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of SyntaxContext) - Return ImportCompletionProviderHelper.CreateContextAsync(document, position, cancellationToken) + Protected Overrides Function CreateContextAsync(document As Document, position As Integer, usePartialSemantic As Boolean, cancellationToken As CancellationToken) As Task(Of SyntaxContext) + Return ImportCompletionProviderHelper.CreateContextAsync(document, position, usePartialSemantic, cancellationToken) End Function Protected Overrides Function GetImportedNamespaces(location As SyntaxNode, semanticModel As SemanticModel, cancellationToken As CancellationToken) As ImmutableArray(Of String) diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ImportCompletionProviderHelper.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ImportCompletionProviderHelper.vb index 9c6703fa937cd..8de0fc9964150 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ImportCompletionProviderHelper.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/ImportCompletionProviderHelper.vb @@ -30,10 +30,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Return builder.ToImmutableAndFree() End Function - Public Shared Async Function CreateContextAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of SyntaxContext) + Public Shared Async Function CreateContextAsync(document As Document, position As Integer, usePartialSemantic As Boolean, cancellationToken As CancellationToken) As Task(Of SyntaxContext) ' Need regular semantic model because we will use it to get imported namespace symbols. Otherwise we will try to ' reach outside of the span And ended up with "node not within syntax tree" error from the speculative model. - Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False) + ' Also we use partial model unless full model is explictly request (e.g. in tests) so that we don't have to wait for all semantics to be computed. + Dim semanticModel = If(usePartialSemantic, + Await document.GetPartialSemanticModelAsync(cancellationToken).ConfigureAwait(False), + Await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(False)) + Contract.ThrowIfNull(semanticModel) Return VisualBasicSyntaxContext.CreateContext(document.Project.Solution.Workspace, semanticModel, position, cancellationToken) End Function End Class diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/TypeImportCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/TypeImportCompletionProvider.vb index 5e3cf4798c8c4..70b9147188e26 100644 --- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/TypeImportCompletionProvider.vb +++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/ImportCompletionProvider/TypeImportCompletionProvider.vb @@ -32,8 +32,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers Public Overrides ReadOnly Property TriggerCharacters As ImmutableHashSet(Of Char) = CompletionUtilities.CommonTriggerCharsAndParen - Protected Overrides Function CreateContextAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of SyntaxContext) - Return ImportCompletionProviderHelper.CreateContextAsync(document, position, cancellationToken) + Protected Overrides Function CreateContextAsync(document As Document, position As Integer, usePartialSemantic As Boolean, cancellationToken As CancellationToken) As Task(Of SyntaxContext) + Return ImportCompletionProviderHelper.CreateContextAsync(document, position, usePartialSemantic, cancellationToken) End Function Protected Overrides Function GetImportedNamespaces(location As SyntaxNode, semanticModel As SemanticModel, cancellationToken As CancellationToken) As ImmutableArray(Of String) diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/TopSyntaxComparer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/TopSyntaxComparer.vb index c6fb5325d9d3e..dde1f58dc3e0c 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/TopSyntaxComparer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/TopSyntaxComparer.vb @@ -11,38 +11,95 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Friend Shared ReadOnly Instance As TopSyntaxComparer = New TopSyntaxComparer() + Private ReadOnly _oldRoot As SyntaxNode + Private ReadOnly _newRoot As SyntaxNode + Private ReadOnly _oldRootChildren As IEnumerable(Of SyntaxNode) + Private ReadOnly _newRootChildren As IEnumerable(Of SyntaxNode) + Private Sub New() End Sub + Public Sub New(oldRoot As SyntaxNode, newRoot As SyntaxNode, oldRootChildren As IEnumerable(Of SyntaxNode), newRootChildren As IEnumerable(Of SyntaxNode)) + ' explicitly listed roots And all their children must be labeled: + Debug.Assert(HasLabel(oldRoot)) + Debug.Assert(HasLabel(newRoot)) + Debug.Assert(oldRootChildren.All(Function(n) HasLabel(n))) + Debug.Assert(newRootChildren.All(Function(n) HasLabel(n))) + + _oldRoot = oldRoot + _newRoot = newRoot + _oldRootChildren = oldRootChildren + _newRootChildren = newRootChildren + + ' the virtual parent of root children must be the respective root + Dim parent As SyntaxNode = Nothing + Debug.Assert(Not TryGetParent(oldRoot, parent)) + Debug.Assert(Not TryGetParent(newRoot, parent)) + Debug.Assert(oldRootChildren.All(Function(node) TryGetParent(node, parent) AndAlso parent Is oldRoot)) + Debug.Assert(newRootChildren.All(Function(node) TryGetParent(node, parent) AndAlso parent Is newRoot)) + End Sub + #Region "Tree Traversal" Protected Overrides Function TryGetParent(node As SyntaxNode, ByRef parent As SyntaxNode) As Boolean - Dim parentNode = node.Parent - parent = parentNode - Return parentNode IsNot Nothing + If node Is _oldRoot OrElse node Is _newRoot Then + parent = Nothing + Return False + End If + + parent = node.Parent + While parent IsNot Nothing AndAlso Not HasLabel(parent) + parent = parent.Parent + End While + + Return parent IsNot Nothing End Function Protected Overrides Function GetChildren(node As SyntaxNode) As IEnumerable(Of SyntaxNode) - Debug.Assert(GetLabel(node) <> IgnoredNode) + Debug.Assert(HasLabel(node)) + + If node Is _oldRoot Then + Return _oldRootChildren + End If + + If node Is _newRoot Then + Return _newRootChildren + End If + Return If(HasChildren(node), EnumerateChildren(node), Nothing) End Function - Private Iterator Function EnumerateChildren(node As SyntaxNode) As IEnumerable(Of SyntaxNode) + Private Shared Iterator Function EnumerateChildren(node As SyntaxNode) As IEnumerable(Of SyntaxNode) For Each child In node.ChildNodesAndTokens() Dim childNode = child.AsNode() - If childNode IsNot Nothing AndAlso GetLabel(childNode) <> IgnoredNode Then + If childNode IsNot Nothing AndAlso HasLabel(childNode) Then Yield childNode End If Next End Function - Protected Overrides Iterator Function GetDescendants(node As SyntaxNode) As IEnumerable(Of SyntaxNode) + Protected Overrides Function GetDescendants(node As SyntaxNode) As IEnumerable(Of SyntaxNode) + Dim rootChildren = If(node Is _oldRoot, _oldRootChildren, If(node Is _newRoot, _newRootChildren, Nothing)) + Return If(rootChildren IsNot Nothing, EnumerateDescendants(rootChildren), EnumerateDescendants(node)) + End Function + + Private Shared Iterator Function EnumerateDescendants(nodes As IEnumerable(Of SyntaxNode)) As IEnumerable(Of SyntaxNode) + For Each node In nodes + Yield node + + For Each descendant In EnumerateDescendants(node) + Yield descendant + Next + Next + End Function + + Private Shared Iterator Function EnumerateDescendants(node As SyntaxNode) As IEnumerable(Of SyntaxNode) For Each descendant In node.DescendantNodesAndTokens( descendIntoChildren:=AddressOf HasChildren, descendIntoTrivia:=False) Dim descendantNode = descendant.AsNode() - If descendantNode IsNot Nothing AndAlso GetLabel(descendantNode) <> IgnoredNode Then + If descendantNode IsNot Nothing AndAlso HasLabel(descendantNode) Then Yield descendantNode End If Next @@ -107,7 +164,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ParameterList ' tied to parent Parameter ' tied to parent - FieldOrParameterName ' tied to grandparent (FieldDeclaration or ParameterList) + FieldOrParameterName ' tied to grand-grandparent (type or method declaration) SimpleAsClause ' tied to parent AttributeList ' tied to parent @@ -152,7 +209,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return 1 Case Label.FieldOrParameterName - Return 2 ' FieldDeclaration or ParameterList + Return 3 ' type or method declaration Case Else Return 0 @@ -334,6 +391,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Classify(kind, isLeaf, ignoreVariableDeclarations) <> Label.Ignored End Function + Friend Shared Function HasLabel(node As SyntaxNode) As Boolean + Return HasLabel(node.Kind, ignoreVariableDeclarations:=False) + End Function + Protected Overrides ReadOnly Property LabelCount As Integer Get Return Label.Count diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index eecc1443df2a2..21e382aa2f5dc 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -42,8 +42,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ''' ''' for methods, constructors, operators and accessors. ''' for auto-properties. - ''' for fields with simple initialization "Dim a = 1", or "Dim a As New C" - ''' for fields with shared AsNew initialization "Dim a, b As New C" or array initializer "Dim a(n), b(n)". + ''' for fields with single identifier in the declaration. + ''' for fields with multiple identifiers in the declaration. ''' A null reference otherwise. ''' Friend Overrides Function FindMemberDeclaration(rootOpt As SyntaxNode, node As SyntaxNode) As SyntaxNode @@ -61,31 +61,28 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return node Case SyntaxKind.PropertyStatement - ' Property [|a As Integer = 1|] - ' Property [|a As New C()|] + ' Property a As Integer = 1 + ' Property a As New T If Not node.Parent.IsKind(SyntaxKind.PropertyBlock) Then Return node End If Case SyntaxKind.VariableDeclarator - If node.Parent.IsKind(SyntaxKind.FieldDeclaration) Then - ' Dim [|a = 0|] - ' Dim [|a = 0|], [|b = 0|] - ' Dim [|b as Integer = 0|] - ' Dim [|v1 As New C|] - ' Dim v1, v2 As New C(Sub [|Goo()|]) + ' Dim a = 0 + ' Dim a(n) = 0 + ' Dim a = 0, b = 0 + ' Dim b as Integer = 0 + ' Dim b(n) as Integer = 0 + ' Dim a As New T + If IsFieldDeclaration(CType(node, VariableDeclaratorSyntax)) Then Return node End If Case SyntaxKind.ModifiedIdentifier - ' Dim [|a(n)|], [|b(n)|] As Integer - ' Dim [|v1|], [|v2|] As New C - If Not node.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) Then - Exit Select - End If - - If DirectCast(node, ModifiedIdentifierSyntax).ArrayBounds IsNot Nothing OrElse - DirectCast(node.Parent, VariableDeclaratorSyntax).Names.Count > 1 Then + ' Dim a, b As T + ' Dim a(n), b(n) As T + ' Dim a, b As New T + If IsFieldDeclaration(CType(node, ModifiedIdentifierSyntax)) Then Return node End If End Select @@ -96,14 +93,28 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Nothing End Function + ''' + ''' Returns true if the node represents a field declaration. + ''' + Private Shared Function IsFieldDeclaration(node As ModifiedIdentifierSyntax) As Boolean + Return node.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) AndAlso DirectCast(node.Parent, VariableDeclaratorSyntax).Names.Count > 1 + End Function + + ''' + ''' Returns true if the node represents a field declaration. + ''' + Private Shared Function IsFieldDeclaration(node As VariableDeclaratorSyntax) As Boolean + Return node.Parent.IsKind(SyntaxKind.FieldDeclaration) AndAlso node.Names.Count = 1 + End Function + ''' - ''' Given a node representing a declaration ( = true) or a top-level edit node ( = false) returns: + ''' Given a node representing a declaration or a top-level edit node returns: ''' - for methods, constructors, operators and accessors. ''' - for auto-properties and fields with initializer or AsNew clause. ''' - for fields with array initializer, e.g. "Dim a(1) As Integer". ''' A null reference otherwise. ''' - Friend Overrides Function TryGetDeclarationBody(node As SyntaxNode, isMember As Boolean) As SyntaxNode + Friend Overrides Function TryGetDeclarationBody(node As SyntaxNode) As SyntaxNode Select Case node.Kind Case SyntaxKind.SubBlock, SyntaxKind.FunctionBlock, @@ -136,43 +147,55 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Nothing End If - ' Dim a = initializer Dim variableDeclarator = DirectCast(node, VariableDeclaratorSyntax) + + Dim body As SyntaxNode = Nothing + If variableDeclarator.Initializer IsNot Nothing Then - Return variableDeclarator.Initializer.Value + ' Dim a = initializer + body = variableDeclarator.Initializer.Value + ElseIf HasAsNewClause(variableDeclarator) Then + ' Dim a As New T + ' Dim a,b As New T + body = DirectCast(variableDeclarator.AsClause, AsNewClauseSyntax).NewExpression End If - If HasAsNewClause(variableDeclarator) Then - ' Dim a As New C() - ' Dim a, b As New C(), but only if the specified node isn't already a member declaration representative. - ' -- This is to handle an edit in AsNew clause because such an edit doesn't affect the modified identifier that would otherwise represent the member. - If variableDeclarator.Names.Count = 1 OrElse Not isMember Then - Return DirectCast(variableDeclarator.AsClause, AsNewClauseSyntax).NewExpression + ' Dim a(n) As T + If variableDeclarator.Names.Count = 1 Then + Dim name = variableDeclarator.Names(0) + + If name.ArrayBounds IsNot Nothing Then + ' Initializer and AsNew clause can't be syntactically specified at the same time, but array bounds can be (it's a semantic error). + ' Guard against such case to maintain consistency and set body to Nothing in that case. + body = If(body Is Nothing, name.ArrayBounds, Nothing) End If End If - Return Nothing + Return body Case SyntaxKind.ModifiedIdentifier If Not node.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) Then Return Nothing End If + Dim modifiedIdentifier = CType(node, ModifiedIdentifierSyntax) + Dim body As SyntaxNode = Nothing + ' Dim a, b As New C() Dim variableDeclarator = DirectCast(node.Parent, VariableDeclaratorSyntax) - If HasMultiAsNewInitializer(variableDeclarator) Then - Return DirectCast(variableDeclarator.AsClause, AsNewClauseSyntax).NewExpression + If HasAsNewClause(variableDeclarator) Then + body = DirectCast(variableDeclarator.AsClause, AsNewClauseSyntax).NewExpression End If - ' Dim a(n) + ' Dim a(n) As Integer ' Dim a(n), b(n) As Integer - ' Dim a(n1, n2, n3) As Integer - Dim modifiedIdentifier = DirectCast(node, ModifiedIdentifierSyntax) If modifiedIdentifier.ArrayBounds IsNot Nothing Then - Return modifiedIdentifier.ArrayBounds + ' AsNew clause can be syntactically specified at the same time as array bounds can be (it's a semantic error). + ' Guard against such case to maintain consistency and set body to Nothing in that case. + body = If(body Is Nothing, modifiedIdentifier.ArrayBounds, Nothing) End If - Return Nothing + Return body Case Else ' Note: A method without body is represented by a SubStatement. @@ -245,14 +268,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Select node End Function - Private Shared Function HasSimpleAsNewInitializer(variableDeclarator As VariableDeclaratorSyntax) As Boolean - Return variableDeclarator.Names.Count = 1 AndAlso HasAsNewClause(variableDeclarator) - End Function - - Private Shared Function HasMultiAsNewInitializer(variableDeclarator As VariableDeclaratorSyntax) As Boolean - Return variableDeclarator.Names.Count > 1 AndAlso HasAsNewClause(variableDeclarator) - End Function - Private Shared Function HasAsNewClause(variableDeclarator As VariableDeclaratorSyntax) As Boolean Return variableDeclarator.AsClause IsNot Nothing AndAlso variableDeclarator.AsClause.IsKind(SyntaxKind.AsNewClause) End Function @@ -300,7 +315,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Nothing Case SyntaxKind.VariableDeclarator - If Not node.Parent.IsKind(SyntaxKind.FieldDeclaration) Then + Dim variableDeclarator = DirectCast(node, VariableDeclaratorSyntax) + If Not IsFieldDeclaration(variableDeclarator) Then Return Nothing End If @@ -311,32 +327,36 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If ' Dim a = initializer - Dim variableDeclarator = DirectCast(node, VariableDeclaratorSyntax) If variableDeclarator.Initializer IsNot Nothing Then Return variableDeclarator.DescendantTokens() End If ' Dim a As New C() - If HasSimpleAsNewInitializer(variableDeclarator) Then + If HasAsNewClause(variableDeclarator) Then + Return variableDeclarator.DescendantTokens() + End If + + ' Dim a(n) As Integer + Dim modifiedIdentifier = variableDeclarator.Names.Single() + If modifiedIdentifier.ArrayBounds IsNot Nothing Then Return variableDeclarator.DescendantTokens() End If Return Nothing Case SyntaxKind.ModifiedIdentifier - If Not node.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) Then + Dim modifiedIdentifier = DirectCast(node, ModifiedIdentifierSyntax) + If Not IsFieldDeclaration(modifiedIdentifier) Then Return Nothing End If ' Dim a, b As New C() Dim variableDeclarator = DirectCast(node.Parent, VariableDeclaratorSyntax) - If HasMultiAsNewInitializer(variableDeclarator) Then + If HasAsNewClause(variableDeclarator) Then Return node.DescendantTokens().Concat(DirectCast(variableDeclarator.AsClause, AsNewClauseSyntax).NewExpression.DescendantTokens()) End If - ' Dim a(n) ' Dim a(n), b(n) As Integer - Dim modifiedIdentifier = DirectCast(node, ModifiedIdentifierSyntax) If modifiedIdentifier.ArrayBounds IsNot Nothing Then Return node.DescendantTokens() End If @@ -522,7 +542,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Debug.Assert(leftSymbol IsNot Nothing) Dim rightProperty = rightType.GetMembers(leftSymbol.Name).Single() - Dim rightDeclaration = DirectCast(GetSymbolSyntax(rightProperty, cancellationToken), PropertyStatementSyntax) + Dim rightDeclaration = DirectCast(rightProperty.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken), PropertyStatementSyntax) rightInitializer = rightDeclaration.Initializer ElseIf leftInitializer.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) Then @@ -533,7 +553,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Debug.Assert(leftSymbol IsNot Nothing) Dim rightSymbol = rightType.GetMembers(leftSymbol.Name).Single() - Dim rightDeclarator = DirectCast(GetSymbolSyntax(rightSymbol, cancellationToken).Parent, VariableDeclaratorSyntax) + Dim rightDeclarator = DirectCast(rightSymbol.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken).Parent, VariableDeclaratorSyntax) rightInitializer = If(leftInitializer.IsKind(SyntaxKind.EqualsValue), rightDeclarator.Initializer, DirectCast(rightDeclarator.AsClause, SyntaxNode)) Else @@ -548,7 +568,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Debug.Assert(leftSymbol IsNot Nothing) Dim rightSymbol = rightType.GetMembers(leftSymbol.Name).Single() - Dim rightIdentifier = DirectCast(GetSymbolSyntax(rightSymbol, cancellationToken), ModifiedIdentifierSyntax) + Dim rightIdentifier = DirectCast(rightSymbol.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken), ModifiedIdentifierSyntax) rightInitializer = rightIdentifier.ArrayBounds.Arguments(argumentIndex) End If @@ -603,6 +623,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return TopSyntaxComparer.Instance.ComputeMatch(oldCompilationUnit, newCompilationUnit) End Function + Protected Overrides Function ComputeTopLevelDeclarationMatch(oldDeclaration As SyntaxNode, newDeclaration As SyntaxNode) As Match(Of SyntaxNode) + Contract.ThrowIfNull(oldDeclaration.Parent) + Contract.ThrowIfNull(newDeclaration.Parent) + + ' Allow matching field declarations represented by a identitifer and the whole variable declarator + ' even when their node kinds do not match. + If oldDeclaration.IsKind(SyntaxKind.ModifiedIdentifier) AndAlso newDeclaration.IsKind(SyntaxKind.VariableDeclarator) Then + oldDeclaration = oldDeclaration.Parent + ElseIf oldDeclaration.IsKind(SyntaxKind.VariableDeclarator) AndAlso newDeclaration.IsKind(SyntaxKind.ModifiedIdentifier) Then + newDeclaration = newDeclaration.Parent + End If + + Dim comparer = New TopSyntaxComparer(oldDeclaration.Parent, newDeclaration.Parent, {oldDeclaration}, {newDeclaration}) + Return comparer.ComputeMatch(oldDeclaration.Parent, newDeclaration.Parent) + End Function + Protected Overrides Function ComputeBodyMatch(oldBody As SyntaxNode, newBody As SyntaxNode, knownMatches As IEnumerable(Of KeyValuePair(Of SyntaxNode, SyntaxNode))) As Match(Of SyntaxNode) SyntaxUtilities.AssertIsBody(oldBody, allowLambda:=True) SyntaxUtilities.AssertIsBody(newBody, allowLambda:=True) @@ -911,7 +947,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Function Friend Overrides Function TryGetContainingTypeDeclaration(node As SyntaxNode) As SyntaxNode - Return node.Parent.FirstAncestorOrSelf(Of TypeBlockSyntax)() + Return node.Parent.FirstAncestorOrSelf(Of TypeBlockSyntax)() ' TODO: EnbumBlock? + End Function + + Friend Overrides Function TryGetAssociatedMemberDeclaration(node As SyntaxNode) As SyntaxNode + Return If(node.IsParentKind(SyntaxKind.PropertyBlock, SyntaxKind.EventBlock), node.Parent, Nothing) End Function Friend Overrides Function HasBackingField(propertyDeclaration As SyntaxNode) As Boolean @@ -959,6 +999,95 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Nothing End Function + ''' + ''' VB symbols return references that represent the declaration statement. + ''' The node that represenets the whole declaration (the block) is the parent node if it exists. + ''' For example, a method with a body is represented by a SubBlock/FunctionBlock while a method without a body + ''' is represented by its declaration statement. + ''' + Protected Overrides Function GetSymbolDeclarationSyntax(reference As SyntaxReference, cancellationToken As CancellationToken) As SyntaxNode + Dim syntax = reference.GetSyntax(cancellationToken) + Dim parent = syntax.Parent + + Select Case syntax.Kind + ' declarations that always have block + + Case SyntaxKind.NamespaceStatement + Debug.Assert(parent.Kind = SyntaxKind.NamespaceBlock) + Return parent + + Case SyntaxKind.ClassStatement + Debug.Assert(parent.Kind = SyntaxKind.ClassBlock) + Return parent + + Case SyntaxKind.StructureStatement + Debug.Assert(parent.Kind = SyntaxKind.StructureBlock) + Return parent + + Case SyntaxKind.InterfaceStatement + Debug.Assert(parent.Kind = SyntaxKind.InterfaceBlock) + Return parent + + Case SyntaxKind.ModuleStatement + Debug.Assert(parent.Kind = SyntaxKind.ModuleBlock) + Return parent + + Case SyntaxKind.EnumStatement + Debug.Assert(parent.Kind = SyntaxKind.EnumBlock) + Return parent + + Case SyntaxKind.SubNewStatement + Debug.Assert(parent.Kind = SyntaxKind.ConstructorBlock) + Return parent + + Case SyntaxKind.OperatorStatement + Debug.Assert(parent.Kind = SyntaxKind.OperatorBlock) + Return parent + + Case SyntaxKind.GetAccessorStatement + Debug.Assert(parent.Kind = SyntaxKind.GetAccessorBlock) + Return parent + + Case SyntaxKind.SetAccessorStatement + Debug.Assert(parent.Kind = SyntaxKind.SetAccessorBlock) + Return parent + + Case SyntaxKind.AddHandlerAccessorStatement + Debug.Assert(parent.Kind = SyntaxKind.AddHandlerAccessorBlock) + Return parent + + Case SyntaxKind.RemoveHandlerAccessorStatement + Debug.Assert(parent.Kind = SyntaxKind.RemoveHandlerAccessorBlock) + Return parent + + Case SyntaxKind.RaiseEventAccessorStatement + Debug.Assert(parent.Kind = SyntaxKind.RaiseEventAccessorBlock) + Return parent + + ' declarations that may or may not have block + + Case SyntaxKind.SubStatement + Return If(parent.Kind = SyntaxKind.SubBlock, parent, syntax) + + Case SyntaxKind.FunctionStatement + Return If(parent.Kind = SyntaxKind.FunctionBlock, parent, syntax) + + ' declarations that never have a block + + Case SyntaxKind.ModifiedIdentifier + Contract.ThrowIfFalse(parent.Parent.IsKind(SyntaxKind.FieldDeclaration)) + Dim variableDeclaration = CType(parent, VariableDeclaratorSyntax) + Return If(variableDeclaration.Names.Count = 1, parent, syntax) + + Case SyntaxKind.VariableDeclarator + ' fields are represented by ModifiedIdentifier: + Throw ExceptionUtilities.UnexpectedValue(syntax.Kind) + + Case Else + Return syntax + End Select + End Function + Friend Overrides Function IsConstructorWithMemberInitializers(declaration As SyntaxNode) As Boolean Dim ctor = TryCast(declaration, ConstructorBlockSyntax) If ctor Is Nothing Then @@ -1007,7 +1136,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue DirectCast(syntaxRefs.Single().GetSyntax(), TypeStatementSyntax).Modifiers.Any(SyntaxKind.PartialKeyword) End Function - Protected Overrides Function GetSymbolForEdit(model As SemanticModel, node As SyntaxNode, editKind As EditKind, editMap As IReadOnlyDictionary(Of SyntaxNode, EditKind), cancellationToken As CancellationToken) As ISymbol + Protected Overrides Function GetSymbolForEdit(model As SemanticModel, + node As SyntaxNode, + editKind As EditKind, + editMap As IReadOnlyDictionary(Of SyntaxNode, EditKind), + ByRef isAmbiguous As Boolean, + cancellationToken As CancellationToken) As ISymbol + + isAmbiguous = False + ' Avoid duplicate semantic edits - don't return symbols for statements within blocks. Select Case node.Kind() Case SyntaxKind.OperatorStatement, @@ -1017,8 +1154,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue SyntaxKind.AddHandlerAccessorStatement, SyntaxKind.RemoveHandlerAccessorStatement, SyntaxKind.RaiseEventAccessorStatement, - SyntaxKind.DeclareSubStatement, - SyntaxKind.DeclareFunctionStatement, SyntaxKind.ClassStatement, SyntaxKind.StructureStatement, SyntaxKind.InterfaceStatement, @@ -1047,27 +1182,37 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Nothing End If - Case SyntaxKind.Parameter + Case SyntaxKind.Parameter, + SyntaxKind.TypeParameter, + SyntaxKind.ImportsStatement, + SyntaxKind.NamespaceBlock Return Nothing Case SyntaxKind.ModifiedIdentifier - If node.Parent.IsKind(SyntaxKind.Parameter) Then + If Not node.Parent.Parent.IsKind(SyntaxKind.FieldDeclaration) Then Return Nothing End If Case SyntaxKind.VariableDeclarator - ' An update to a field variable declarator might either be - ' 1) variable declarator update (an initializer is changes) - ' 2) modified identifier update (an array bound changes) - ' Handle the first one here. - If editKind = editKind.Update AndAlso node.Parent.IsKind(SyntaxKind.FieldDeclaration) Then - ' If multiple fields are defined by this declaration pick the first one. - ' We want to analyze the associated initializer just once. Any of the fields is good. - node = DirectCast(node, VariableDeclaratorSyntax).Names.First() + If Not node.Parent.IsKind(SyntaxKind.FieldDeclaration) Then + Return Nothing End If + + Dim variableDeclarator = CType(node, VariableDeclaratorSyntax) + isAmbiguous = variableDeclarator.Names.Count > 1 + node = variableDeclarator.Names.First End Select - Return model.GetDeclaredSymbol(node, cancellationToken) + Dim symbol = model.GetDeclaredSymbol(node, cancellationToken) + + ' Ignore partial method definition parts. + ' Partial method that does not have implementation part is not emitted to metadata. + ' Partial method without a definition part is a compilation error. + If symbol IsNot Nothing AndAlso symbol.Kind = SymbolKind.Method AndAlso CType(symbol, IMethodSymbol).IsPartialDefinition Then + Return Nothing + End If + + Return symbol End Function Friend Overrides Function ContainsLambda(declaration As SyntaxNode) As Boolean @@ -1226,7 +1371,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue SyntaxKind.FunctionBlock, SyntaxKind.OperatorBlock, SyntaxKind.ConstructorBlock, - SyntaxKind.EventBlock, SyntaxKind.SetAccessorBlock, SyntaxKind.GetAccessorBlock, SyntaxKind.AddHandlerAccessorBlock, @@ -1234,6 +1378,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue SyntaxKind.RaiseEventAccessorBlock Return GetDiagnosticSpan(DirectCast(node, MethodBlockBaseSyntax).BlockStatement) + Case SyntaxKind.EventBlock + Return GetDiagnosticSpan(DirectCast(node, EventBlockSyntax).EventStatement) + Case SyntaxKind.SubStatement, SyntaxKind.FunctionStatement, SyntaxKind.OperatorStatement, @@ -1598,9 +1745,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue SyntaxKind.OperatorStatement Return FeaturesResources.operator_ - Case SyntaxKind.ConstructorBlock, - SyntaxKind.SubNewStatement - Return FeaturesResources.constructor + Case SyntaxKind.ConstructorBlock + Return If(CType(node, ConstructorBlockSyntax).SubNewStatement.Modifiers.Any(SyntaxKind.SharedKeyword), VBFeaturesResources.shared_constructor, FeaturesResources.constructor) + + Case SyntaxKind.SubNewStatement + Return If(CType(node, SubNewStatementSyntax).Modifiers.Any(SyntaxKind.SharedKeyword), VBFeaturesResources.Shared_constructor, FeaturesResources.constructor) Case SyntaxKind.PropertyBlock @@ -1824,7 +1973,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Case EditKind.Move - ReportError(RudeEditKind.Move) + ClassifyMove(_newNode) Return Case EditKind.Insert @@ -1841,6 +1990,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Sub #Region "Move and Reorder" + Private Sub ClassifyMove(newNode As SyntaxNode) + Select Case newNode.Kind + Case SyntaxKind.ModifiedIdentifier, + SyntaxKind.VariableDeclarator + ' Identifier can be moved within the same type declaration. + ' Determine validity of such change in semantic analysis. + Return + + Case Else + ReportError(RudeEditKind.Move) + End Select + End Sub + Private Sub ClassifyReorder(oldNode As SyntaxNode, newNode As SyntaxNode) Select Case newNode.Kind Case SyntaxKind.OptionStatement, @@ -1885,8 +2047,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Case SyntaxKind.PropertyStatement, SyntaxKind.FieldDeclaration, - SyntaxKind.EventStatement, - SyntaxKind.VariableDeclarator + SyntaxKind.EventStatement ' Maybe we could allow changing order of field declarations unless the containing type layout is sequential, ' and it's not a COM interface. ReportError(RudeEditKind.Move) @@ -1903,11 +2064,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ReportError(RudeEditKind.Move) Return - Case SyntaxKind.ModifiedIdentifier - ' Identifier can only moved from one VariableDeclarator to another if both are part of - ' the same declaration. We could allow these moves if the order and types of variables - ' didn't change. - ReportError(RudeEditKind.Move) + Case SyntaxKind.ModifiedIdentifier, + SyntaxKind.VariableDeclarator + ' Identifier can be moved within the same type declaration. + ' Determine validity of such change in semantic analysis. Return Case Else @@ -1927,93 +2087,37 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Case SyntaxKind.ClassBlock, - SyntaxKind.StructureBlock - ClassifyTypeWithPossibleExternMembersInsert(DirectCast(node, TypeBlockSyntax)) - Return - - Case SyntaxKind.InterfaceBlock - ClassifyTypeInsert(DirectCast(node, TypeBlockSyntax).BlockStatement.Modifiers) - Return - - Case SyntaxKind.EnumBlock - ClassifyTypeInsert(DirectCast(node, EnumBlockSyntax).EnumStatement.Modifiers) - Return - - Case SyntaxKind.ModuleBlock - ' Modules can't be nested or private - ReportError(RudeEditKind.Insert) - Return - - Case SyntaxKind.DelegateSubStatement, - SyntaxKind.DelegateFunctionStatement - ClassifyTypeInsert(DirectCast(node, DelegateStatementSyntax).Modifiers) - Return - - Case SyntaxKind.SubStatement, ' interface method - SyntaxKind.FunctionStatement ' interface method - ReportError(RudeEditKind.Insert) - Return - - Case SyntaxKind.PropertyBlock - ClassifyModifiedMemberInsert(DirectCast(node, PropertyBlockSyntax).PropertyStatement.Modifiers) - Return - - Case SyntaxKind.PropertyStatement ' autoprop or interface property - ' We don't need to check whether the container is an interface, since we disallow - ' adding public methods And all methods in interface declarations are public. - ClassifyModifiedMemberInsert(DirectCast(node, PropertyStatementSyntax).Modifiers) - Return - - Case SyntaxKind.EventBlock - ClassifyModifiedMemberInsert(DirectCast(node, EventBlockSyntax).EventStatement.Modifiers) - Return - - Case SyntaxKind.EventStatement - ClassifyModifiedMemberInsert(DirectCast(node, EventStatementSyntax).Modifiers) - Return - - Case SyntaxKind.OperatorBlock - ReportError(RudeEditKind.InsertOperator) - Return - - Case SyntaxKind.SubBlock, - SyntaxKind.FunctionBlock - ClassifyMethodInsert(DirectCast(node, MethodBlockSyntax).SubOrFunctionStatement) - Return - - Case SyntaxKind.DeclareSubStatement, - SyntaxKind.DeclareFunctionStatement - ' CLR doesn't support adding P/Invokes - ReportError(RudeEditKind.Insert) - Return - - Case SyntaxKind.ConstructorBlock - If SyntaxUtilities.IsParameterlessConstructor(node) Then - Return - End If - - ClassifyModifiedMemberInsert(DirectCast(node, MethodBlockBaseSyntax).BlockStatement.Modifiers) - Return - - Case SyntaxKind.GetAccessorBlock, + SyntaxKind.StructureBlock, + SyntaxKind.InterfaceBlock, + SyntaxKind.EnumBlock, + SyntaxKind.ModuleBlock, + SyntaxKind.DelegateSubStatement, + SyntaxKind.DelegateFunctionStatement, + SyntaxKind.SubStatement, ' interface method + SyntaxKind.FunctionStatement, ' interface method + SyntaxKind.PropertyBlock, + SyntaxKind.PropertyStatement, ' autoprop or interface property + SyntaxKind.EventBlock, + SyntaxKind.EventStatement, + SyntaxKind.OperatorBlock, + SyntaxKind.SubBlock, + SyntaxKind.FunctionBlock, + SyntaxKind.DeclareSubStatement, + SyntaxKind.DeclareFunctionStatement, + SyntaxKind.ConstructorBlock, + SyntaxKind.GetAccessorBlock, SyntaxKind.SetAccessorBlock, SyntaxKind.AddHandlerAccessorBlock, SyntaxKind.RemoveHandlerAccessorBlock, - SyntaxKind.RaiseEventAccessorBlock - Return - - Case SyntaxKind.FieldDeclaration - ClassifyFieldInsert(DirectCast(node, FieldDeclarationSyntax)) + SyntaxKind.RaiseEventAccessorBlock, + SyntaxKind.FieldDeclaration, + SyntaxKind.ModifiedIdentifier Return Case SyntaxKind.VariableDeclarator ' Ignore, errors will be reported for children (ModifiedIdentifier, AsClause) Return - Case SyntaxKind.ModifiedIdentifier - ClassifyFieldInsert(DirectCast(node, ModifiedIdentifierSyntax)) - Return - Case SyntaxKind.EnumMemberDeclaration, SyntaxKind.TypeParameter, SyntaxKind.StructureConstraint, @@ -2041,80 +2145,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Sub - Private Function ClassifyModifiedMemberInsert(modifiers As SyntaxTokenList) As Boolean - If modifiers.Any(SyntaxKind.OverridableKeyword) OrElse - modifiers.Any(SyntaxKind.MustOverrideKeyword) OrElse - modifiers.Any(SyntaxKind.OverridesKeyword) Then - - ReportError(RudeEditKind.InsertVirtual) - Return False - End If - - Return True - End Function - - Private Function ClassifyTypeInsert(modifiers As SyntaxTokenList) As Boolean - Return ClassifyModifiedMemberInsert(modifiers) - End Function - - Private Sub ClassifyTypeWithPossibleExternMembersInsert(type As TypeBlockSyntax) - If Not ClassifyTypeInsert(type.BlockStatement.Modifiers) Then - Return - End If - - For Each member In type.Members - Dim modifiers As SyntaxTokenList - - Select Case member.Kind - Case SyntaxKind.DeclareFunctionStatement, - SyntaxKind.DeclareSubStatement - ReportError(RudeEditKind.Insert, member, member) - - Case SyntaxKind.PropertyStatement - modifiers = DirectCast(member, PropertyStatementSyntax).Modifiers - - Case SyntaxKind.SubBlock, - SyntaxKind.FunctionBlock - modifiers = DirectCast(member, MethodBlockBaseSyntax).BlockStatement.Modifiers - End Select - - ' TODO: DllImport/Declare? - 'If (modifiers.Any(SyntaxKind.MustOverrideKeyword)) Then - ' ReportError(RudeEditKind.InsertMustOverride, member, member) - 'End If - Next - End Sub - - Private Sub ClassifyMethodInsert(method As MethodStatementSyntax) - If method.TypeParameterList IsNot Nothing Then - ReportError(RudeEditKind.InsertGenericMethod) - End If - - If method.HandlesClause IsNot Nothing Then - ReportError(RudeEditKind.InsertHandlesClause) - End If - - ClassifyModifiedMemberInsert(method.Modifiers) - End Sub - - Private Sub ClassifyFieldInsert(field As FieldDeclarationSyntax) - ' Can't insert WithEvents field since it is effectively a virtual property. - If field.Modifiers.Any(SyntaxKind.WithEventsKeyword) Then - ReportError(RudeEditKind.Insert) - Return - End If - - Dim containingType = field.Parent - If containingType.IsKind(SyntaxKind.ModuleBlock) Then - ReportError(RudeEditKind.Insert) - Return - End If - End Sub - - Private Sub ClassifyFieldInsert(fieldVariableName As ModifiedIdentifierSyntax) - ClassifyFieldInsert(DirectCast(fieldVariableName.Parent.Parent, FieldDeclarationSyntax)) - End Sub - Private Sub ClassifyParameterInsert(parameterList As ParameterListSyntax) ' Sub M -> Sub M() is ok If parameterList.Parameters.Count = 0 Then @@ -2131,9 +2161,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Select Case oldNode.Kind Case SyntaxKind.OptionStatement, SyntaxKind.ImportsStatement, - SyntaxKind.AttributesStatement, SyntaxKind.NamespaceBlock, - SyntaxKind.ClassBlock, + SyntaxKind.AttributesStatement + ReportError(RudeEditKind.Delete) + Return + + Case SyntaxKind.ClassBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.ModuleBlock, @@ -2152,30 +2185,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue SyntaxKind.EventBlock, SyntaxKind.EventStatement, SyntaxKind.DeclareSubStatement, - SyntaxKind.DeclareFunctionStatement - ' To allow removal of declarations we would need to update method bodies that - ' were previously binding to them but now are binding to another symbol that was previously hidden. - ReportError(RudeEditKind.Delete) - Return - - Case SyntaxKind.ConstructorBlock - ' Allow deletion of a parameterless constructor. - ' Semantic analysis reports an error if the parameterless ctor isn't replaced by a default ctor. - If Not SyntaxUtilities.IsParameterlessConstructor(oldNode) Then - ReportError(RudeEditKind.Delete) - End If - + SyntaxKind.DeclareFunctionStatement, + SyntaxKind.ConstructorBlock + ' We do not report member delete here since the member might be moving to a different part of a partial type declaration. + ' If that is not the case the semantic analysis reports the rude edit. Return Case SyntaxKind.GetAccessorBlock, SyntaxKind.SetAccessorBlock, SyntaxKind.AddHandlerAccessorBlock, SyntaxKind.RemoveHandlerAccessorBlock, - SyntaxKind.RaiseEventAccessorBlock - ' An accessor can be removed. Accessors are not hiding other symbols. - ' If the new compilation still uses the removed accessor a semantic error will be reported. - ' For simplicity though we disallow deletion of accessors for now. - ReportError(RudeEditKind.Delete) + SyntaxKind.RaiseEventAccessorBlock, + SyntaxKind.EnumMemberDeclaration + ' We do not report error here since it will be reported in semantic analysis. Return Case SyntaxKind.AttributeList, @@ -2186,12 +2208,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ReportError(RudeEditKind.Delete) Return - Case SyntaxKind.EnumMemberDeclaration - ' We could allow removing enum member if it didn't affect the values of other enum members. - ' If the updated compilation binds without errors it means that the enum value wasn't used. - ReportError(RudeEditKind.Delete) - Return - Case SyntaxKind.TypeParameter, SyntaxKind.TypeParameterList, SyntaxKind.Parameter, @@ -2400,13 +2416,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return End If - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then + If Not AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore:=SyntaxKind.PartialKeyword) Then ReportError(RudeEditKind.ModifiersUpdate) Return End If - Debug.Assert(Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier)) - ReportError(RudeEditKind.Renamed) + If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then + ReportError(RudeEditKind.Renamed) + End If End Sub Private Sub ClassifyUpdate(oldNode As TypeBlockSyntax, newNode As TypeBlockSyntax) @@ -2776,6 +2793,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ClassifyUpdate(oldNode.Identifier, newNode.Identifier) End Sub + Private Shared Function AreModifiersEquivalent(oldModifiers As SyntaxTokenList, newModifiers As SyntaxTokenList, ignore As SyntaxKind) As Boolean + Dim oldIgnoredModifierIndex = oldModifiers.IndexOf(ignore) + Dim newIgnoredModifierIndex = newModifiers.IndexOf(ignore) + + If oldIgnoredModifierIndex >= 0 Then + oldModifiers = oldModifiers.RemoveAt(oldIgnoredModifierIndex) + End If + + If newIgnoredModifierIndex >= 0 Then + newModifiers = newModifiers.RemoveAt(newIgnoredModifierIndex) + End If + + Return SyntaxFactory.AreEquivalent(oldModifiers, newModifiers) + End Function + Private Sub ClassifyMethodBodyRudeUpdate(oldBody As MethodBlockBaseSyntax, newBody As MethodBlockBaseSyntax, containingMethod As MethodBlockSyntax, @@ -2832,10 +2864,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue #End Region End Structure - Friend Overrides Sub ReportTopLevelSyntacticRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), - match As Match(Of SyntaxNode), - edit As Edit(Of SyntaxNode), - editMap As Dictionary(Of SyntaxNode, EditKind)) + Friend Overrides Sub ReportTopLevelSyntacticRudeEdits( + diagnostics As ArrayBuilder(Of RudeEditDiagnostic), + match As Match(Of SyntaxNode), + edit As Edit(Of SyntaxNode), + editMap As Dictionary(Of SyntaxNode, EditKind)) ' For most nodes we ignore Insert and Delete edits if their parent was also inserted or deleted, respectively. ' For ModifiedIdentifiers though we check the grandparent instead because variables can move across @@ -2880,27 +2913,146 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue #End Region #Region "Semantic Rude Edits" - Friend Overrides Sub ReportInsertedMemberSymbolRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), newSymbol As ISymbol) - ' CLR doesn't support adding P/Invokes. - - ' VB needs to check if the type doesn't contain methods with DllImport attribute. - If newSymbol.IsKind(SymbolKind.NamedType) Then - For Each member In DirectCast(newSymbol, INamedTypeSymbol).GetMembers() - ReportDllImportInsertRudeEdit(diagnostics, member) - Next - Else - ReportDllImportInsertRudeEdit(diagnostics, newSymbol) + Friend Overrides Sub ReportInsertedMemberSymbolRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), newSymbol As ISymbol, newNode As SyntaxNode, insertingIntoExistingContainingType As Boolean) + Dim kind = GetInsertedMemberSymbolRudeEditKind(newSymbol, insertingIntoExistingContainingType) + + If kind <> RudeEditKind.None Then + diagnostics.Add(New RudeEditDiagnostic( + kind, + GetDiagnosticSpan(newNode, EditKind.Insert), + newNode, + arguments:={GetDisplayName(newNode, EditKind.Insert)})) End If End Sub - Private Shared Sub ReportDllImportInsertRudeEdit(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), member As ISymbol) - If member.IsKind(SymbolKind.Method) AndAlso - DirectCast(member, IMethodSymbol).GetDllImportData() IsNot Nothing Then + Private Shared Function GetInsertedMemberSymbolRudeEditKind(newSymbol As ISymbol, insertingIntoExistingContainingType As Boolean) As RudeEditKind + Select Case newSymbol.Kind + Case SymbolKind.Method + Dim method = DirectCast(newSymbol, IMethodSymbol) - diagnostics.Add(New RudeEditDiagnostic(RudeEditKind.InsertDllImport, - member.Locations.First().SourceSpan)) + ' Inserting P/Invoke into a new or existing type is not allowed. + If method.GetDllImportData() IsNot Nothing Then + Return RudeEditKind.InsertDllImport + End If + + ' Inserting method with handles clause into a new or existing type is not allowed. + If Not method.HandledEvents.IsEmpty Then + Return RudeEditKind.InsertHandlesClause + End If + + Case SymbolKind.NamedType + Dim type = CType(newSymbol, INamedTypeSymbol) + + ' Inserting module is not allowed. + If type.TypeKind = TypeKind.Module Then + Return RudeEditKind.Insert + End If + End Select + + ' All rude edits below only apply when inserting into an existing type (not when the type itself is inserted): + If Not insertingIntoExistingContainingType Then + Return RudeEditKind.None + End If + + If newSymbol.ContainingType.Arity > 0 AndAlso newSymbol.Kind <> SymbolKind.NamedType Then + Return RudeEditKind.InsertIntoGenericType + End If + + ' Inserting virtual or interface member is not allowed. + If (newSymbol.IsVirtual Or newSymbol.IsOverride Or newSymbol.IsAbstract) AndAlso newSymbol.Kind <> SymbolKind.NamedType Then + Return RudeEditKind.InsertVirtual End If + + Select Case newSymbol.Kind + Case SymbolKind.Method + Dim method = DirectCast(newSymbol, IMethodSymbol) + + ' Inserting generic method into an existing type is not allowed. + If method.Arity > 0 Then + Return RudeEditKind.InsertGenericMethod + End If + + ' Inserting operator to an existing type is not allowed. + If method.MethodKind = MethodKind.Conversion OrElse method.MethodKind = MethodKind.UserDefinedOperator Then + Return RudeEditKind.InsertOperator + End If + + Return RudeEditKind.None + + Case SymbolKind.Field + 'Dim field = DirectCast(newSymbol, IFieldSymbol) + ' TODO: + ' Can't insert WithEvents field since it is effectively a virtual property. + ' WithEvents X As C + 'If field.Modifiers.Any(SyntaxKind.WithEventsKeyword) Then + ' ReportError(RudeEditKind.Insert) + ' Return + 'End If + + 'Dim containingType = field.Parent + 'If containingType.IsKind(SyntaxKind.ModuleBlock) Then + ' ReportError(RudeEditKind.Insert) + ' Return + 'End If + Return RudeEditKind.None + End Select + + Return RudeEditKind.None + End Function + + Friend Overrides Sub ReportTypeDeclarationInsertDeleteRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), oldType As INamedTypeSymbol, newType As INamedTypeSymbol, newDeclaration As SyntaxNode, cancellationToken As CancellationToken) + Dim oldNodes = ArrayBuilder(Of SyntaxNode).GetInstance() + Dim newNodes = ArrayBuilder(Of SyntaxNode).GetInstance() + + Dim report = + Sub(addNodes As Action(Of ArrayBuilder(Of SyntaxNode), TypeBlockSyntax), rudeEditKind As RudeEditKind) + + For Each syntaxRef In oldType.DeclaringSyntaxReferences + addNodes(oldNodes, CType(GetSymbolDeclarationSyntax(syntaxRef, cancellationToken), TypeBlockSyntax)) + Next + + For Each syntaxRef In newType.DeclaringSyntaxReferences + addNodes(newNodes, CType(GetSymbolDeclarationSyntax(syntaxRef, cancellationToken), TypeBlockSyntax)) + Next + + If oldNodes.Count <> newNodes.Count OrElse + oldNodes.Zip(newNodes, Function(oldNode, newNode) SyntaxFactory.AreEquivalent(oldNode, newNode)).Any(Function(isEquivalent) Not isEquivalent) Then + + diagnostics.Add(New RudeEditDiagnostic( + rudeEditKind, + GetDiagnosticSpan(newDeclaration, EditKind.Update), + newDeclaration, + arguments:={GetDisplayName(newDeclaration, EditKind.Update)})) + End If + + oldNodes.Clear() + newNodes.Clear() + End Sub + + ' Consider better error messages + report(Sub(b, t) AddNodes(b, t.BlockStatement.AttributeLists), RudeEditKind.Update) + report(Sub(b, t) AddNodes(b, t.BlockStatement.TypeParameterList?.Parameters), RudeEditKind.Update) + + report(Sub(b, t) + For Each inherit In t.Inherits + For Each baseType In inherit.Types + b.Add(baseType) + Next + Next + End Sub, RudeEditKind.BaseTypeOrInterfaceUpdate) + + report(Sub(b, t) + For Each impl In t.Implements + For Each baseInterface In impl.Types + b.Add(baseInterface) + Next + Next + End Sub, RudeEditKind.BaseTypeOrInterfaceUpdate) + + oldNodes.Free() + newNodes.Free() End Sub + #End Region #Region "Exception Handling Rude Edits" diff --git a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb index 5c1d8cbf00b3e..097ae9ed190e0 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodAsynchronous/VisualBasicMakeMethodAsynchronousCodeFixProvider.vb @@ -7,6 +7,7 @@ Imports System.Composition Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.MakeMethodAsynchronous +Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous @@ -78,7 +79,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodAsynchronous If Not IsTaskLike(methodSymbol.ReturnType, knownTypes) Then ' if the current return type is not already task-list, then wrap it in Task(of ...) - Dim returnType = knownTypes._taskOfTType.Construct(methodSymbol.ReturnType).GenerateTypeSyntax() + Dim returnType = knownTypes._taskOfTType.Construct(methodSymbol.ReturnType).GenerateTypeSyntax().WithAdditionalAnnotations(Simplifier.AddImportsAnnotation) newFunctionStatement = newFunctionStatement.WithAsClause( newFunctionStatement.AsClause.WithType(returnType)) End If diff --git a/src/Features/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.Features.vbproj b/src/Features/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.Features.vbproj index 37bb1a1fdfbfc..9472c85c4d2be 100644 --- a/src/Features/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.Features.vbproj +++ b/src/Features/VisualBasic/Portable/Microsoft.CodeAnalysis.VisualBasic.Features.vbproj @@ -31,7 +31,6 @@ - diff --git a/src/Features/VisualBasic/Portable/VBFeaturesResources.resx b/src/Features/VisualBasic/Portable/VBFeaturesResources.resx index b15e103c6496e..c96f2c59e3e89 100644 --- a/src/Features/VisualBasic/Portable/VBFeaturesResources.resx +++ b/src/Features/VisualBasic/Portable/VBFeaturesResources.resx @@ -1252,4 +1252,7 @@ Sub(<parameterList>) <statement> ({0} Events) + + Shared constructor + \ No newline at end of file diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf index b9e2973e5caaa..0beb96dd6f5e9 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.cs.xlf @@ -102,6 +102,11 @@ Odebrat klíčové slovo Shared ze členu modulu {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. Sem vložte název, kterým deklarujete novou položku. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf index 5c485230d8b86..68737b6962a3a 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.de.xlf @@ -102,6 +102,11 @@ Schlüsselwort "Shared" aus Modulmember entfernen {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. Geben Sie hier einen Namen ein, um ein neues Feld zu deklarieren. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf index 9b4cc118bfca2..406ae78325ba2 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.es.xlf @@ -102,6 +102,11 @@ Quitar la palabra clave "Shared" del miembro del módulo {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. Escriba aquí un nombre para declarar un nuevo campo. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf index 8c1a5feddf8cd..9a0f1ac27cd59 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.fr.xlf @@ -102,6 +102,11 @@ Supprimer le mot clé 'Shared' du membre de module {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. Tapez un nom ici pour déclarer un nouveau champ. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf index 33d4cb185b466..d08b361d3c59f 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.it.xlf @@ -102,6 +102,11 @@ Rimuovi la parola chiave 'Shared' dal membro Module {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. Digitare qui un nome per dichiarare un nuovo campo. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf index d085f938ebdbc..58bd3c15ec8ac 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ja.xlf @@ -102,6 +102,11 @@ モジュール メンバーから 'Shared' キーワードを削除 {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. 新しいフィールドを宣言するには、ここに名前を入力します。 diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf index 3c56f2126e73a..14dc33ccf67ca 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ko.xlf @@ -102,6 +102,11 @@ 모듈 멤버에서 'Shared' 키워드 제거 {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. 여기에 이름을 입력하여 새 필드를 선언하세요. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf index 8464d256ffded..cb61d441eb216 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pl.xlf @@ -102,6 +102,11 @@ Usuń słowo kluczowe "Shared" ze składowej modułu {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. Wpisz tutaj nazwę, aby utworzyć deklarację nowego pola. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf index 4056616e3f37e..8be8d14a2064a 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.pt-BR.xlf @@ -102,6 +102,11 @@ Remover a palavra-chave 'Shared' do membro do Módulo {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. Digite um nome aqui para declarar um novo campo. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf index 44e240371bfc2..eac901e155ddf 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.ru.xlf @@ -102,6 +102,11 @@ Удалить ключевое слово "Shared" из элемента модуля {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. Введите здесь имя для объявления нового поля. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf index d0bec07409824..883138c04a59f 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.tr.xlf @@ -102,6 +102,11 @@ Modül üyesinden 'Shared' anahtar sözcüğünü kaldır {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. Yeni bir alan bildirmek için buraya bir ad yazın. diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf index 7120b1e4c0645..a6773ed214ba0 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hans.xlf @@ -102,6 +102,11 @@ 从模块成员中删除 "Shared" 关键字 {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. 请在此处键入名称以声明新字段。 diff --git a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf index 6778c2f3d784d..483e284ff7309 100644 --- a/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf +++ b/src/Features/VisualBasic/Portable/xlf/VBFeaturesResources.zh-Hant.xlf @@ -102,6 +102,11 @@ 從模組成員移除 'Shared' 關鍵字 {Locked="Shared"} "Shared" is a VB keyword and should not be localized. + + Shared constructor + Shared constructor + + Type a name here to declare a new field. 於此輸入名稱以宣告新的欄位。 diff --git a/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs b/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs index 9a86808f42d82..59a27b0e8093e 100644 --- a/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs +++ b/src/Tools/AnalyzerRunner/IncrementalAnalyzerRunner.cs @@ -55,7 +55,7 @@ public async Task RunAsync(CancellationToken cancellationToken) if (usePersistentStorage) { var persistentStorageService = _workspace.Services.GetRequiredService(); - using var persistentStorage = await persistentStorageService.GetStorageAsync(_workspace.CurrentSolution, cancellationToken).ConfigureAwait(false); + await using var persistentStorage = await persistentStorageService.GetStorageAsync(_workspace.CurrentSolution, cancellationToken).ConfigureAwait(false); if (persistentStorage is NoOpPersistentStorage) { throw new InvalidOperationException("Benchmark is not configured to use persistent storage."); diff --git a/src/Tools/BuildValidator/BuildConstructor.cs b/src/Tools/BuildValidator/BuildConstructor.cs index 7919972107383..3ace6906e4958 100644 --- a/src/Tools/BuildValidator/BuildConstructor.cs +++ b/src/Tools/BuildValidator/BuildConstructor.cs @@ -12,11 +12,13 @@ using System.Reflection.PortableExecutable; using System.Text; using System.Threading.Tasks; +using Microsoft.Cci; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.VisualBasic; using Microsoft.Extensions.Logging; +using Roslyn.Utilities; using CS = Microsoft.CodeAnalysis.CSharp; using VB = Microsoft.CodeAnalysis.VisualBasic; @@ -38,12 +40,16 @@ public BuildConstructor(LocalReferenceResolver referenceResolver, LocalSourceRes _logger = logger; } - public Compilation CreateCompilation(CompilationOptionsReader compilationOptionsReader, string name) + public Compilation? CreateCompilation(CompilationOptionsReader compilationOptionsReader, string fileName) { - var pdbCompilationOptions = compilationOptionsReader.GetMetadataCompilationOptions(); - if (pdbCompilationOptions.Length == 0) + // We try to handle assemblies missing compilation options gracefully by skipping them. + // However, if an assembly has some bad combination of data, for example if it contains + // compilation options but not metadata references, then we throw an exception. + if (!compilationOptionsReader.TryGetMetadataCompilationOptions(out var pdbCompilationOptions) + || pdbCompilationOptions.Length == 0) { - throw new InvalidDataException("Did not find compilation options in pdb"); + _logger.LogInformation($"{fileName} did not contain compilation options in its PDB"); + return null; } var metadataReferenceInfos = compilationOptionsReader.GetMetadataReferences(); @@ -60,14 +66,30 @@ public Compilation CreateCompilation(CompilationOptionsReader compilationOptions if (pdbCompilationOptions.TryGetUniqueOption("language", out var language)) { + var diagnosticBag = DiagnosticBag.GetInstance(); var compilation = language switch { - LanguageNames.CSharp => CreateCSharpCompilation(name, compilationOptionsReader, sources, metadataReferences), - LanguageNames.VisualBasic => CreateVisualBasicCompilation(name, compilationOptionsReader, sources, metadataReferences), + LanguageNames.CSharp => CreateCSharpCompilation(fileName, compilationOptionsReader, sources, metadataReferences), + LanguageNames.VisualBasic => CreateVisualBasicCompilation(fileName, compilationOptionsReader, sources, metadataReferences, diagnosticBag), _ => throw new InvalidDataException($"{language} is not a known language") }; - return compilation; + var diagnostics = diagnosticBag.ToReadOnlyAndFree(); + var hadError = false; + foreach (var diagnostic in diagnostics) + { + if (diagnostic.Severity == DiagnosticSeverity.Error) + { + _logger.LogError(diagnostic.ToString()); + hadError = true; + } + else + { + _logger.LogWarning(diagnostic.ToString()); + } + } + + return hadError ? null : compilation; } throw new InvalidDataException("Did not find language in compilation options"); @@ -88,7 +110,10 @@ void logResolvedSources() { var sourceFileInfo = resolvedSource.SourceFileInfo; var hash = BitConverter.ToString(sourceFileInfo.Hash).Replace("-", ""); - _logger.LogInformation($@"""{resolvedSource.DisplayPath}"" - {sourceFileInfo.HashAlgorithm} - {hash}"); + var embeddedCompressedHash = sourceFileInfo.EmbeddedCompressedHash is { } compressedHash + ? ("[uncompressed]" + BitConverter.ToString(compressedHash).Replace("-", "")) + : null; + _logger.LogInformation($@"""{resolvedSource.DisplayPath}"" - {sourceFileInfo.HashAlgorithm} - {hash} - {embeddedCompressedHash}"); } } } @@ -100,6 +125,7 @@ private ImmutableArray ResolveSourceLinks(CompilationOptionsReader c if (sourceLinks.IsDefault) { _logger.LogInformation("No source links found in pdb"); + sourceLinks = ImmutableArray.Empty; } else { @@ -129,26 +155,27 @@ private ImmutableArray ResolveSources( #region CSharp private Compilation CreateCSharpCompilation( - string assemblyName, + string fileName, CompilationOptionsReader optionsReader, ImmutableArray sources, ImmutableArray metadataReferences) { - var (compilationOptions, parseOptions) = CreateCSharpCompilationOptions(optionsReader, assemblyName); + var (compilationOptions, parseOptions) = CreateCSharpCompilationOptions(optionsReader, fileName); return CSharpCompilation.Create( - assemblyName, + Path.GetFileNameWithoutExtension(fileName), syntaxTrees: sources.Select(s => CSharpSyntaxTree.ParseText(s.SourceText, options: parseOptions, path: s.SourceFileInfo.SourceFilePath)).ToImmutableArray(), references: metadataReferences, options: compilationOptions); } - private (CSharpCompilationOptions, CSharpParseOptions) CreateCSharpCompilationOptions(CompilationOptionsReader optionsReader, string assemblyName) + private (CSharpCompilationOptions, CSharpParseOptions) CreateCSharpCompilationOptions(CompilationOptionsReader optionsReader, string fileName) { using var scope = _logger.BeginScope("Options"); var pdbCompilationOptions = optionsReader.GetMetadataCompilationOptions(); var langVersionString = pdbCompilationOptions.GetUniqueOption("language-version"); var optimization = pdbCompilationOptions.GetUniqueOption("optimization"); + // TODO: Check portability policy if needed // pdbCompilationOptions.TryGetValue("portability-policy", out var portabilityPolicyString); pdbCompilationOptions.TryGetUniqueOption(_logger, "define", out var define); @@ -175,9 +202,7 @@ private Compilation CreateCSharpCompilation( optionsReader.GetOutputKind(), reportSuppressedDiagnostics: false, - // TODO: can't rely on the implicity moduleName here. In the case of .NET Core EXE the output name will - // end with .dll but the inferred name will be .exe - moduleName: assemblyName + ".dll", + moduleName: fileName, mainTypeName: optionsReader.GetMainTypeName(), scriptClassName: null, usings: null, @@ -213,10 +238,10 @@ private Compilation CreateCSharpCompilation( return (compilationOptions, parseOptions); } - private static (OptimizationLevel, bool) GetOptimizationLevel(string optimizationLevel) + private static (OptimizationLevel, bool) GetOptimizationLevel(string? optimizationLevel) => optimizationLevel switch { - "debug" => (OptimizationLevel.Debug, false), + null or "debug" => (OptimizationLevel.Debug, false), "debug-plus" => (OptimizationLevel.Debug, true), "release" => (OptimizationLevel.Release, false), _ => throw new InvalidDataException($"Optimization \"{optimizationLevel}\" level not recognized") @@ -226,62 +251,70 @@ private static (OptimizationLevel, bool) GetOptimizationLevel(string optimizatio #region Visual Basic private Compilation CreateVisualBasicCompilation( - string assemblyName, + string fileName, CompilationOptionsReader optionsReader, ImmutableArray sources, - ImmutableArray metadataReferences) + ImmutableArray metadataReferences, + DiagnosticBag diagnosticBag) { - var compilationOptions = CreateVisualBasicCompilationOptions(optionsReader); + var compilationOptions = CreateVisualBasicCompilationOptions(optionsReader, fileName, diagnosticBag); return VisualBasicCompilation.Create( - assemblyName, - syntaxTrees: sources.Select(s => VisualBasicSyntaxTree.ParseText(s.SourceText, options: compilationOptions.ParseOptions, path: s.DisplayPath)).ToImmutableArray(), + Path.GetFileNameWithoutExtension(fileName), + syntaxTrees: sources.Select(s => VisualBasicSyntaxTree.ParseText(s.SourceText, options: compilationOptions.ParseOptions, path: s.SourceFileInfo.SourceFilePath)).ToImmutableArray(), references: metadataReferences, options: compilationOptions); } - private static VisualBasicCompilationOptions CreateVisualBasicCompilationOptions(CompilationOptionsReader optionsReader) + private static VisualBasicCompilationOptions CreateVisualBasicCompilationOptions(CompilationOptionsReader optionsReader, string fileName, DiagnosticBag diagnosticBag) { var pdbCompilationOptions = optionsReader.GetMetadataCompilationOptions(); - var langVersionString = pdbCompilationOptions.GetUniqueOption("language-version"); - var optimization = pdbCompilationOptions.GetUniqueOption("optimization"); - pdbCompilationOptions.TryGetUniqueOption("define", out var define); - pdbCompilationOptions.TryGetUniqueOption("strict", out var strict); - pdbCompilationOptions.TryGetUniqueOption("checked", out var checkedString); + var langVersionString = pdbCompilationOptions.GetUniqueOption(CompilationOptionNames.LanguageVersion); + pdbCompilationOptions.TryGetUniqueOption(CompilationOptionNames.Optimization, out var optimization); + pdbCompilationOptions.TryGetUniqueOption(CompilationOptionNames.GlobalNamespaces, out var globalNamespacesString); + + IEnumerable? globalImports = null; + if (!string.IsNullOrEmpty(globalNamespacesString)) + { + globalImports = GlobalImport.Parse(globalNamespacesString.Split(';')); + } VB.LanguageVersion langVersion = default; VB.LanguageVersionFacts.TryParse(langVersionString, ref langVersion); - var preprocessorSymbols = string.IsNullOrEmpty(define) - ? Array.Empty>() - : define.Split(';') - .Select(s => s.Split('=')) - .Select(a => new KeyValuePair(a[0], a[1])) - .ToArray(); - - var parseOptions = VisualBasicParseOptions.Default.WithLanguageVersion(langVersion) - .WithPreprocessorSymbols(preprocessorSymbols); + IReadOnlyDictionary? preprocessorSymbols = null; + if (OptionToString(CompilationOptionNames.Define) is string defineString) + { + preprocessorSymbols = VisualBasicCommandLineParser.ParseConditionalCompilationSymbols(defineString, out var diagnostics); + if (diagnostics is object) + { + diagnosticBag.AddRange(diagnostics); + } + } - var (optimizationLevel, _) = GetOptimizationLevel(optimization); + var parseOptions = VisualBasicParseOptions + .Default + .WithLanguageVersion(langVersion) + .WithPreprocessorSymbols(preprocessorSymbols.ToImmutableArrayOrEmpty()); - bool.TryParse(checkedString, out var isChecked); - bool.TryParse(strict, out var isStrict); + var (optimizationLevel, plus) = GetOptimizationLevel(optimization); + var isChecked = OptionToBool(CompilationOptionNames.Checked) ?? true; + var embedVBRuntime = OptionToBool(CompilationOptionNames.EmbedRuntime) ?? false; + var rootNamespace = OptionToString(CompilationOptionNames.RootNamespace); - // TODO: rebuilding VB projects fails due to reference issues - // for example, core types like KeyValuePair are missing - return new VisualBasicCompilationOptions( - OutputKind.DynamicallyLinkedLibrary, - moduleName: null, - mainTypeName: null, + var compilationOptions = new VisualBasicCompilationOptions( + optionsReader.GetOutputKind(), + moduleName: fileName, + mainTypeName: optionsReader.GetMainTypeName(), scriptClassName: "Script", - globalImports: null, - rootNamespace: null, - optionStrict: isStrict ? OptionStrict.On : OptionStrict.Off, - optionInfer: true, - optionExplicit: true, - optionCompareText: false, + globalImports: globalImports, + rootNamespace: rootNamespace, + optionStrict: OptionToEnum(CompilationOptionNames.OptionStrict) ?? OptionStrict.Off, + optionInfer: OptionToBool(CompilationOptionNames.OptionInfer) ?? false, + optionExplicit: OptionToBool(CompilationOptionNames.OptionExplicit) ?? false, + optionCompareText: OptionToBool(CompilationOptionNames.OptionCompareText) ?? false, parseOptions: parseOptions, - embedVbCoreRuntime: false, + embedVbCoreRuntime: embedVBRuntime, optimizationLevel: optimizationLevel, checkOverflow: isChecked, cryptoKeyContainer: null, @@ -301,6 +334,15 @@ private static VisualBasicCompilationOptions CreateVisualBasicCompilationOptions publicSign: false, reportSuppressedDiagnostics: false, metadataImportOptions: MetadataImportOptions.Public); + compilationOptions.DebugPlusMode = plus; + + return compilationOptions; + + string? OptionToString(string option) => pdbCompilationOptions.TryGetUniqueOption(option, out var value) ? value : null; + bool? OptionToBool(string option) => pdbCompilationOptions.TryGetUniqueOption(option, out var value) ? ToBool(value) : null; + T? OptionToEnum(string option) where T : struct => pdbCompilationOptions.TryGetUniqueOption(option, out var value) ? ToEnum(value) : null; + static bool? ToBool(string value) => bool.TryParse(value, out var boolValue) ? boolValue : null; + static T? ToEnum(string value) where T : struct => Enum.TryParse(value, out var enumValue) ? enumValue : null; } #endregion } diff --git a/src/Tools/BuildValidator/BuildValidator.csproj b/src/Tools/BuildValidator/BuildValidator.csproj index 1634c165010d1..b91c3e61f545c 100644 --- a/src/Tools/BuildValidator/BuildValidator.csproj +++ b/src/Tools/BuildValidator/BuildValidator.csproj @@ -4,7 +4,7 @@ Exe - netcoreapp3.1;net472 + net472;netcoreapp3.1 AnyCPU enable true @@ -22,7 +22,9 @@ + + diff --git a/src/Tools/BuildValidator/CompilationDiff.cs b/src/Tools/BuildValidator/CompilationDiff.cs index ee3828d61bfd7..40da3839dbf38 100644 --- a/src/Tools/BuildValidator/CompilationDiff.cs +++ b/src/Tools/BuildValidator/CompilationDiff.cs @@ -11,6 +11,7 @@ using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.WindowsRuntime; using System.Security.Cryptography; using System.Text; using System.Threading; @@ -18,20 +19,34 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; +using Microsoft.DiaSymReader.Tools; using Microsoft.Extensions.Logging; using Microsoft.Metadata.Tools; namespace BuildValidator { - internal class CompilationDiff + internal sealed class CompilationDiff { + public record BuildInfo( + byte[] AssemblyBytes, + PEReader AssemblyReader, + MetadataReader PdbMetadataReader) + { + public MetadataReader AssemblyMetadataReader { get; } = AssemblyReader.GetMetadataReader(); + } + + public record BuildDataFiles( + string AssemblyMdvFilePath, + string PdbMdvFilePath, + string PdbXmlFilePath, + string ILFilePath, + string CustomDataFilePath); + public bool? AreEqual { get; } public string OriginalPath { get; } public ImmutableArray Diagnostics { get; } - private CompilationDiff( - string originalPath, - bool? areEqual) + private CompilationDiff(string originalPath, bool? areEqual) { AreEqual = areEqual; OriginalPath = originalPath; @@ -43,6 +58,11 @@ private CompilationDiff(ImmutableArray diagnostics, string originalP OriginalPath = originalPath; } + public static CompilationDiff CreatePlaceholder(FileInfo originalBinaryPath) + { + return new CompilationDiff(originalBinaryPath.FullName, areEqual: null); + } + public static unsafe CompilationDiff Create( FileInfo originalBinaryPath, CompilationOptionsReader optionsReader, @@ -61,6 +81,13 @@ public static unsafe CompilationDiff Create( iconInIcoFormat: null); var sourceLink = optionsReader.GetSourceLinkUTF8(); + + var embeddedTexts = producedCompilation.SyntaxTrees + .Select(st => (path: st.FilePath, text: st.GetText())) + .Where(pair => pair.text.CanBeEmbedded) + .Select(pair => EmbeddedText.FromSource(pair.path, pair.text)) + .ToImmutableArray(); + var emitResult = producedCompilation.Emit( peStream: rebuildPeStream, pdbStream: null, @@ -73,10 +100,7 @@ public static unsafe CompilationDiff Create( metadataPEStream: null, pdbOptionsBlobReader: optionsReader.GetMetadataCompilationOptionsBlobReader(), sourceLinkStream: sourceLink != null ? new MemoryStream(sourceLink) : null, - embeddedTexts: producedCompilation.SyntaxTrees - .Select(st => (path: st.FilePath, text: st.GetText())) - .Where(pair => pair.text.CanBeEmbedded) - .Select(pair => EmbeddedText.FromSource(pair.path, pair.text)), + embeddedTexts: embeddedTexts, cancellationToken: CancellationToken.None); if (!emitResult.Success) @@ -109,81 +133,183 @@ public static unsafe CompilationDiff Create( var debugPath = options.DebugPath; logger.LogInformation($@"Writing diffs to ""{Path.GetFullPath(debugPath)}"""); - var assemblyName = Path.GetFileNameWithoutExtension(originalBinaryPath.Name); - var assemblyDebugPath = Path.Combine(debugPath, assemblyName); - - var originalPath = Path.Combine(assemblyDebugPath, "original"); - var rebuildPath = Path.Combine(assemblyDebugPath, "rebuild"); - var sourcesPath = Path.Combine(assemblyDebugPath, "sources"); + fixed (byte* ptr = rebuildBytes) + { + using var rebuildPeReader = new PEReader(ptr, rebuildBytes.Length); + var originalInfo = new BuildInfo( + AssemblyBytes: originalBytes, + AssemblyReader: optionsReader.PeReader, + PdbMetadataReader: optionsReader.PdbReader); - Directory.CreateDirectory(originalPath); - Directory.CreateDirectory(rebuildPath); - Directory.CreateDirectory(sourcesPath); + var rebuildInfo = new BuildInfo( + AssemblyBytes: rebuildBytes, + AssemblyReader: rebuildPeReader, + PdbMetadataReader: rebuildPeReader.GetEmbeddedPdbMetadataReader()); - // TODO: output source files should include the entire relative path instead of just the file name. - foreach (var tree in producedCompilation.SyntaxTrees) - { - var sourceFilePath = Path.Combine(sourcesPath, Path.GetFileName(tree.FilePath)); - using var file = File.OpenWrite(sourceFilePath); - var writer = new StreamWriter(file); - tree.GetText().Write(writer); - writer.Flush(); + createDiffArtifacts(debugPath, originalBinaryPath.Name, originalInfo, rebuildInfo, producedCompilation); + SearchForKnownIssues(logger, originalInfo, rebuildInfo); } + } + } - var originalAssemblyPath = Path.Combine(originalPath, originalBinaryPath.Name); - File.WriteAllBytes(originalAssemblyPath, originalBytes); + return new CompilationDiff(originalBinaryPath.FullName, bytesEqual); + } - var rebuildAssemblyPath = Path.Combine(rebuildPath, originalBinaryPath.Name); - File.WriteAllBytes(rebuildAssemblyPath, rebuildBytes); + static void createDiffArtifacts(string debugPath, string assemblyFileName, BuildInfo originalInfo, BuildInfo rebuildInfo, Compilation compilation) + { + var assemblyName = Path.GetFileNameWithoutExtension(assemblyFileName); + var assemblyDebugPath = Path.Combine(debugPath, assemblyName); + Directory.CreateDirectory(assemblyDebugPath); - var originalPeMdvPath = Path.Combine(originalPath, assemblyName + ".pe.mdv"); - var originalPdbMdvPath = Path.Combine(originalPath, assemblyName + ".pdb.mdv"); - writeVisualization(originalPeMdvPath, optionsReader.PeReader.GetMetadataReader()); - writeVisualization(originalPdbMdvPath, optionsReader.PdbReader); + var originalDataFiles = createBuildArtifacts(Path.Combine(assemblyDebugPath, "original"), assemblyFileName, originalInfo); + var rebuildDataFiles = createBuildArtifacts(Path.Combine(assemblyDebugPath, "rebuild"), assemblyFileName, rebuildInfo); - var rebuildPeMdvPath = Path.Combine(rebuildPath, assemblyName + ".pe.mdv"); - var rebuildPdbMdvPath = Path.Combine(rebuildPath, assemblyName + ".pdb.mdv"); - fixed (byte* ptr = rebuildBytes) - { - using var rebuildPeReader = new PEReader(ptr, rebuildBytes.Length); - writeVisualization(rebuildPeMdvPath, rebuildPeReader.GetMetadataReader()); - - if (rebuildPeReader.TryOpenAssociatedPortablePdb( - rebuildAssemblyPath, - path => File.Exists(path) ? File.OpenRead(path) : null, - out var provider, - out _) && provider is { }) - { - var rebuildPdbReader = provider.GetMetadataReader(MetadataReaderOptions.Default); - writeVisualization(rebuildPdbMdvPath, rebuildPdbReader); - } - } + createDiffScript("compare-pe.mdv.ps1", originalDataFiles.AssemblyMdvFilePath, rebuildDataFiles.AssemblyMdvFilePath); + createDiffScript("compare-pdb.mdv.ps1", originalDataFiles.PdbMdvFilePath, rebuildDataFiles.PdbMdvFilePath); + createDiffScript("compare-pdb.xml.ps1", originalDataFiles.PdbXmlFilePath, rebuildDataFiles.PdbXmlFilePath); + createDiffScript("compare-il.ps1", originalDataFiles.ILFilePath, rebuildDataFiles.ILFilePath); + + void createDiffScript(string scriptName, string originalFilePath, string rebuildFilePath) + { + originalFilePath = getRelativePath(originalFilePath); + rebuildFilePath = getRelativePath(rebuildFilePath); + + File.WriteAllText(Path.Combine(assemblyDebugPath, scriptName), $@"code --diff (Join-Path $PSScriptRoot ""{originalFilePath}"") (Join-Path $PSScriptRoot ""{rebuildFilePath}"")"); + string getRelativePath(string dataFilePath) => dataFilePath.Substring(assemblyDebugPath.Length); + } + + var sourcesPath = Path.Combine(assemblyDebugPath, "sources"); + Directory.CreateDirectory(sourcesPath); + + // TODO: output source files should include the entire relative path instead of just the file name. + foreach (var tree in compilation.SyntaxTrees) + { + var sourceFilePath = Path.Combine(sourcesPath, Path.GetFileName(tree.FilePath)); + using var file = File.OpenWrite(sourceFilePath); + var writer = new StreamWriter(file); + tree.GetText().Write(writer); + writer.Flush(); + } + } + + static BuildDataFiles createBuildArtifacts(string outputPath, string assemblyFileName, BuildInfo buildInfo) + { + var assemblyName = Path.GetFileNameWithoutExtension(assemblyFileName); + var assemblyFilePath = Path.Combine(outputPath, assemblyFileName); + var buildDataFiles = new BuildDataFiles( + AssemblyMdvFilePath: Path.Combine(outputPath, assemblyName + ".mdv"), + PdbMdvFilePath: Path.Combine(outputPath, assemblyName + ".pdb.mdv"), + ILFilePath: Path.Combine(outputPath, assemblyName + ".il"), + PdbXmlFilePath: Path.Combine(outputPath, assemblyName + ".pdb.xml"), + CustomDataFilePath: Path.Combine(outputPath, "custom-data.txt")); + + Directory.CreateDirectory(outputPath); + File.WriteAllBytes(assemblyFilePath, buildInfo.AssemblyBytes); + + // This is deliberately named .extracted.pdb instead of .pdb. A number of tools will look + // for a PDB with the name assemblyName.pdb. Want to make explicitly sure that does not + // happen and such tools always correctly fall back to the embedded PDB. + var pdbFilePath = Path.Combine(outputPath, assemblyName + ".extracted.pdb"); + writeAllBytes(pdbFilePath, new Span(buildInfo.PdbMetadataReader.MetadataPointer, buildInfo.PdbMetadataReader.MetadataLength)); + + createMetadataVisualization(buildDataFiles.AssemblyMdvFilePath, buildInfo.AssemblyMetadataReader); + createMetadataVisualization(buildDataFiles.PdbMdvFilePath, buildInfo.PdbMetadataReader); + createDataFile(buildDataFiles.CustomDataFilePath, buildInfo.AssemblyReader, buildInfo.PdbMetadataReader); + + var pdbToXmlOptions = PdbToXmlOptions.ResolveTokens + | PdbToXmlOptions.ThrowOnError + | PdbToXmlOptions.ExcludeScopes + | PdbToXmlOptions.IncludeSourceServerInformation + | PdbToXmlOptions.IncludeEmbeddedSources + | PdbToXmlOptions.IncludeTokens + | PdbToXmlOptions.IncludeMethodSpans; + + using var pdbXmlStream = File.Create(buildDataFiles.PdbXmlFilePath); + PdbToXmlConverter.ToXml( + new StreamWriter(pdbXmlStream), + pdbStream: new UnmanagedMemoryStream(buildInfo.PdbMetadataReader.MetadataPointer, buildInfo.PdbMetadataReader.MetadataLength), + peStream: new MemoryStream(buildInfo.AssemblyBytes), + options: pdbToXmlOptions, + methodName: null); - var ildasmOriginalOutputPath = Path.Combine(originalPath, assemblyName + ".il"); - var ildasmRebuildOutputPath = Path.Combine(rebuildPath, assemblyName + ".il"); + Process.Start(IldasmUtilities.IldasmPath, $@"{assemblyFilePath} /all /out={buildDataFiles.ILFilePath}").WaitForExit(); - // TODO: can we bundle ildasm in with the utility? - Process.Start(@"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\ildasm.exe", $@"{originalBinaryPath.FullName} /out={ildasmOriginalOutputPath}").WaitForExit(); - Process.Start(@"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\ildasm.exe", $@"{rebuildAssemblyPath} /out={ildasmRebuildOutputPath}").WaitForExit(); + return buildDataFiles; + } + + static void writeAllBytes(string filePath, Span span) + { + using var tempFile = File.OpenWrite(filePath); + tempFile.Write(span); + } + + static void createMetadataVisualization(string outputFilePath, MetadataReader metadataReader) + { + using var writer = new StreamWriter(outputFilePath, append: false); + var visualizer = new MetadataVisualizer(metadataReader, writer); + visualizer.Visualize(); + writer.Flush(); + } + + // Used to write any data that could be interesting for debugging purposes + static void createDataFile(string outputFilePath, PEReader peReader, MetadataReader pdbMetadataReader) + { + using var writer = new StreamWriter(outputFilePath, append: false); + var peMetadataReader = peReader.GetMetadataReader(); - File.WriteAllText(Path.Combine(assemblyDebugPath, "compare-pe.mdv.ps1"), $@"code --diff (Join-Path $PSScriptRoot ""{originalPeMdvPath.Substring(assemblyDebugPath.Length)}"") (Join-Path $PSScriptRoot ""{rebuildPeMdvPath.Substring(assemblyDebugPath.Length)}"")"); - File.WriteAllText(Path.Combine(assemblyDebugPath, "compare-pdb.mdv.ps1"), $@"code --diff (Join-Path $PSScriptRoot ""{originalPdbMdvPath.Substring(assemblyDebugPath.Length)}"") (Join-Path $PSScriptRoot ""{rebuildPdbMdvPath.Substring(assemblyDebugPath.Length)}"")"); - File.WriteAllText(Path.Combine(assemblyDebugPath, "compare-il.ps1"), $@"code --diff (Join-Path $PSScriptRoot ""{ildasmOriginalOutputPath.Substring(assemblyDebugPath.Length)}"") (Join-Path $PSScriptRoot ""{ildasmRebuildOutputPath.Substring(assemblyDebugPath.Length)}"")"); + writeDebugDirectory(); + writeEmbeddedFileInfo(); + + void writeDebugDirectory() + { + writer.WriteLine("Debug Directory"); + foreach (var debugDirectory in peReader.ReadDebugDirectory()) + { + writer.WriteLine($"\ttype:{debugDirectory.Type} dataSize:{debugDirectory.DataSize} dataPointer:{debugDirectory.DataPointer} dataRelativeVirtualAddress:{debugDirectory.DataRelativeVirtualAddress}"); } } - return new CompilationDiff(originalBinaryPath.FullName, bytesEqual); + void writeEmbeddedFileInfo() + { + writer.WriteLine("Embedded File Info"); + var optionsReader = new CompilationOptionsReader(EmptyLogger.Instance, pdbMetadataReader, peReader); + var sourceFileInfos = optionsReader.GetSourceFileInfos(optionsReader.GetEncoding()); + foreach (var info in sourceFileInfos) + { + if (info.EmbeddedCompressedHash is { } hash) + { + var hashString = BitConverter.ToString(hash).Replace("-", ""); + writer.WriteLine($@"\t""{Path.GetFileName(info.SourceFilePath)}"" - {hashString}"); + } + } + } } + } + + /// + /// Given two builds which are not identical this will look for known issues that could be + /// causing the difference. + /// + private static unsafe bool SearchForKnownIssues(ILogger logger, BuildInfo originalInfo, BuildInfo rebuildInfo) + { + return hasPdbCompressionDifferences(); - void writeVisualization(string outPath, MetadataReader pdbReader) + bool hasPdbCompressionDifferences() { - using (var tempFile = File.OpenWrite(outPath)) + var originalEntry = originalInfo.AssemblyReader.ReadDebugDirectory().Single(x => x.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); + var rebuildEntry = rebuildInfo.AssemblyReader.ReadDebugDirectory().Single(x => x.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); + if (originalEntry.DataSize != rebuildEntry.DataSize) { - var writer = new StreamWriter(tempFile); - var visualizer = new MetadataVisualizer(pdbReader, writer); - visualizer.Visualize(); - writer.Flush(); + var originalPdbSpan = new Span(originalInfo.PdbMetadataReader.MetadataPointer, originalInfo.PdbMetadataReader.MetadataLength); + var rebuildPdbSpan = new Span(rebuildInfo.PdbMetadataReader.MetadataPointer, rebuildInfo.PdbMetadataReader.MetadataLength); + if (originalPdbSpan.SequenceEqual(rebuildPdbSpan)) + { + logger.LogError($"Known issue: different compression used for embedded portable pdb debug directory entry"); + return true; + } } + + return false; } } } diff --git a/src/Tools/BuildValidator/CompilationOptionsReader.cs b/src/Tools/BuildValidator/CompilationOptionsReader.cs index 9b9cd0f14776e..61d9ec0535406 100644 --- a/src/Tools/BuildValidator/CompilationOptionsReader.cs +++ b/src/Tools/BuildValidator/CompilationOptionsReader.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Linq; @@ -27,17 +28,20 @@ internal readonly struct SourceFileInfo internal SourceHashAlgorithm HashAlgorithm { get; } internal byte[] Hash { get; } internal SourceText? EmbeddedText { get; } + internal byte[]? EmbeddedCompressedHash { get; } internal SourceFileInfo( string sourceFilePath, SourceHashAlgorithm hashAlgorithm, byte[] hash, - SourceText? embeddedText) + SourceText? embeddedText, + byte[]? embeddedCompressedHash) { SourceFilePath = sourceFilePath; HashAlgorithm = hashAlgorithm; Hash = hash; EmbeddedText = embeddedText; + EmbeddedCompressedHash = embeddedCompressedHash; } } @@ -74,14 +78,29 @@ public CompilationOptionsReader(ILogger logger, MetadataReader pdbReader, PERead PeReader = peReader; } + public bool TryGetMetadataCompilationOptionsBlobReader(out BlobReader reader) + { + return TryGetCustomDebugInformationBlobReader(CompilationOptionsGuid, out reader); + } + public BlobReader GetMetadataCompilationOptionsBlobReader() { - if (!TryGetCustomDebugInformationBlobReader(CompilationOptionsGuid, out var optionsBlob)) + if (!TryGetMetadataCompilationOptionsBlobReader(out var reader)) { throw new InvalidOperationException(); } + return reader; + } + + public bool TryGetMetadataCompilationOptions([NotNullWhen(true)] out MetadataCompilationOptions? options) + { + if (_metadataCompilationOptions is null && TryGetMetadataCompilationOptionsBlobReader(out var optionsBlob)) + { + _metadataCompilationOptions = new MetadataCompilationOptions(ParseCompilationOptions(optionsBlob)); + } - return optionsBlob; + options = _metadataCompilationOptions; + return options != null; } public MetadataCompilationOptions GetMetadataCompilationOptions() @@ -192,7 +211,7 @@ public OutputKind GetOutputKind() => return (typeName, methodName); } - private SourceText? ResolveEmbeddedSource(DocumentHandle document, SourceHashAlgorithm hashAlgorithm, Encoding encoding) + private (SourceText? embeddedText, byte[]? compressedHash) ResolveEmbeddedSource(DocumentHandle document, SourceHashAlgorithm hashAlgorithm, Encoding encoding) { byte[] bytes = (from handle in PdbReader.GetCustomDebugInformation(document) let cdi = PdbReader.GetCustomDebugInformation(handle) @@ -201,14 +220,18 @@ where PdbReader.GetGuid(cdi.Kind) == EmbeddedSourceGuid if (bytes == null) { - return null; + return default; } int uncompressedSize = BitConverter.ToInt32(bytes, 0); var stream = new MemoryStream(bytes, sizeof(int), bytes.Length - sizeof(int)); + byte[]? compressedHash = null; if (uncompressedSize != 0) { + using var algorithm = CryptographicHashProvider.TryGetAlgorithm(hashAlgorithm) ?? throw new InvalidOperationException(); + compressedHash = algorithm.ComputeHash(bytes); + var decompressed = new MemoryStream(uncompressedSize); using (var deflater = new DeflateStream(stream, CompressionMode.Decompress)) @@ -227,7 +250,8 @@ where PdbReader.GetGuid(cdi.Kind) == EmbeddedSourceGuid using (stream) { // todo: IVT and EncodedStringText.Create? - return SourceText.From(stream, encoding: encoding, checksumAlgorithm: hashAlgorithm, canBeEmbedded: true); + var embeddedText = SourceText.From(stream, encoding: encoding, checksumAlgorithm: hashAlgorithm, canBeEmbedded: true); + return (embeddedText, compressedHash); } } @@ -293,7 +317,7 @@ public ImmutableArray GetSourceFileInfos(Encoding encoding) var hash = PdbReader.GetBlobBytes(document.Hash); var embeddedContent = ResolveEmbeddedSource(documentHandle, hashAlgorithm, encoding); - builder.Add(new SourceFileInfo(name, hashAlgorithm, hash, embeddedContent)); + builder.Add(new SourceFileInfo(name, hashAlgorithm, hash, embeddedContent.embeddedText, embeddedContent.compressedHash)); } return builder.MoveToImmutable(); diff --git a/src/Tools/BuildValidator/DemoLogger.cs b/src/Tools/BuildValidator/DemoLogger.cs index 055a3088cd868..b7f3e12087ffb 100644 --- a/src/Tools/BuildValidator/DemoLogger.cs +++ b/src/Tools/BuildValidator/DemoLogger.cs @@ -57,4 +57,21 @@ public void Dispose() { } } + + internal sealed class EmptyLogger : ILogger, IDisposable + { + public static EmptyLogger Instance { get; } = new EmptyLogger(); + + public void Dispose() + { + } + + public IDisposable BeginScope(TState state) => this; + + public bool IsEnabled(LogLevel logLevel) => false; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + } + } } diff --git a/src/Tools/BuildValidator/Extensions.cs b/src/Tools/BuildValidator/Extensions.cs new file mode 100644 index 0000000000000..a312a8c22a809 --- /dev/null +++ b/src/Tools/BuildValidator/Extensions.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; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; + +namespace BuildValidator +{ + internal static class Extensions + { +#if !NETCOREAPP + internal static unsafe void Write(this FileStream stream, ReadOnlySpan span) + { + fixed (byte* dataPointer = span) + { + using var unmanagedStream = new UnmanagedMemoryStream(dataPointer, span.Length); + unmanagedStream.CopyTo(stream); + } + } +#endif + + internal static MetadataReader GetEmbeddedPdbMetadataReader(this PEReader peReader) + { + var entry = peReader.ReadDebugDirectory().Single(x => x.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); + var provider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry); + return provider.GetMetadataReader(); + } + } +} diff --git a/src/Tools/BuildValidator/IldasmUtilities.cs b/src/Tools/BuildValidator/IldasmUtilities.cs new file mode 100644 index 0000000000000..8204473a95087 --- /dev/null +++ b/src/Tools/BuildValidator/IldasmUtilities.cs @@ -0,0 +1,42 @@ +// 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.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using Roslyn.Utilities; + +namespace BuildValidator +{ + internal static class IldasmUtilities + { + private static string GetIldasmPath() + { + var ildasmExeName = PlatformInformation.IsWindows ? "ildasm.exe" : "ildasm"; + var directory = Path.GetDirectoryName(typeof(IldasmUtilities).GetTypeInfo().Assembly.Location) ?? throw new DirectoryNotFoundException(); + string ridName; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + ridName = "win-x64"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + ridName = "osx-x64"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + ridName = "linux-x64"; + } + else + { + throw new PlatformNotSupportedException("Runtime platform not supported for testing"); + } + + return Path.Combine(directory, "runtimes", ridName, "native", ildasmExeName); + } + + internal static readonly string IldasmPath = GetIldasmPath(); + } +} diff --git a/src/Tools/BuildValidator/LocalReferenceResolver.cs b/src/Tools/BuildValidator/LocalReferenceResolver.cs index 72de32d852b86..1e7ffb872ddea 100644 --- a/src/Tools/BuildValidator/LocalReferenceResolver.cs +++ b/src/Tools/BuildValidator/LocalReferenceResolver.cs @@ -30,11 +30,10 @@ internal class LocalReferenceResolver public LocalReferenceResolver(Options options, ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); - foreach (var directoryInfo in GetRefAssembliesDirectories()) + foreach (var path in options.AssembliesPaths) { - _indexDirectories.Add(directoryInfo); + _indexDirectories.Add(new DirectoryInfo(path)); } - _indexDirectories.Add(new DirectoryInfo(options.AssembliesPath)); _indexDirectories.Add(GetNugetCacheDirectory()); foreach (var path in options.ReferencesPaths) { @@ -44,7 +43,7 @@ public LocalReferenceResolver(Options options, ILoggerFactory loggerFactory) using var _ = _logger.BeginScope("Assembly Reference Search Paths"); foreach (var directory in _indexDirectories) { - _logger.LogInformation($@"""{directory}"""); + _logger.LogInformation($@"""{directory.FullName}"""); } } @@ -59,16 +58,6 @@ public static DirectoryInfo GetNugetCacheDirectory() return new DirectoryInfo(nugetPackageDirectory); } - public static DirectoryInfo[] GetRefAssembliesDirectories() - { - // TODO: Don't hardcode the paths here. - return new[] - { - new DirectoryInfo(@"C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref"), - new DirectoryInfo(@"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref") - }; - } - public string GetReferencePath(MetadataReferenceInfo referenceInfo) { if (_cache.TryGetValue(referenceInfo.Mvid, out var value)) @@ -136,7 +125,7 @@ public void CacheNames(ImmutableArray names) { _logger.LogError($@"{missingReference.Name} - {missingReference.Mvid}"); } - throw new FileNotFoundException(); + throw new Exception($"Cannot resolve {uncached.Length} references"); } } diff --git a/src/Tools/BuildValidator/Options.cs b/src/Tools/BuildValidator/Options.cs index b7a23f5e1c982..31f269ff34fc7 100644 --- a/src/Tools/BuildValidator/Options.cs +++ b/src/Tools/BuildValidator/Options.cs @@ -10,7 +10,7 @@ namespace BuildValidator { internal record Options( - string AssembliesPath, + string[] AssembliesPaths, string[] ReferencesPaths, string SourcePath, bool Verbose, diff --git a/src/Tools/BuildValidator/Program.cs b/src/Tools/BuildValidator/Program.cs index 941eea0f318b1..33ead1ddf3790 100644 --- a/src/Tools/BuildValidator/Program.cs +++ b/src/Tools/BuildValidator/Program.cs @@ -43,14 +43,14 @@ static int Main(string[] args) var rootCommand = new RootCommand { new Option( - "--assembliesPath", "Path to assemblies to rebuild" - ) { IsRequired = true }, + "--assembliesPath", "Path to assemblies to rebuild (can be specified one or more times)" + ) { IsRequired = true, Argument = { Arity = ArgumentArity.OneOrMore } }, new Option( "--sourcePath", "Path to sources to use in rebuild" ) { IsRequired = true }, - new Option( - "--referencesPaths", "Additional paths to referenced assemblies" - ), + new Option( + "--referencesPath", "Path to referenced assemblies (can be specified zero or more times)" + ) { Argument = { Arity = ArgumentArity.ZeroOrMore } }, new Option( "--verbose", "Output verbose log information" ), @@ -64,32 +64,30 @@ static int Main(string[] args) "--debugPath", "Path to output debug info. Defaults to the user temp directory. Note that a unique debug path should be specified for every instance of the tool running with `--debug` enabled." ) }; - rootCommand.Handler = CommandHandler.Create(HandleCommand); + rootCommand.Handler = CommandHandler.Create(HandleCommand); return rootCommand.Invoke(args); } - static int HandleCommand(string assembliesPath, string sourcePath, string[]? referencesPaths, bool verbose, bool quiet, bool debug, string? debugPath) + static int HandleCommand(string[] assembliesPath, string sourcePath, string[]? referencesPath, bool verbose, bool quiet, bool debug, string? debugPath) { // If user provided a debug path then assume we should write debug outputs. debug |= debugPath is object; debugPath ??= Path.Combine(Path.GetTempPath(), $"BuildValidator"); - referencesPaths ??= Array.Empty(); + referencesPath ??= Array.Empty(); - var options = new Options(assembliesPath, referencesPaths, sourcePath, verbose, quiet, debug, debugPath); + var options = new Options(assembliesPath, referencesPath, sourcePath, verbose, quiet, debug, debugPath); - // TODO: remove the DemoLoggerProvider, update this dependency, - // and move to the built in logger. - var loggerFactory = new LoggerFactory( - new[] { new ConsoleLoggerProvider(new ConsoleLoggerSettings()) }, - new LoggerFilterOptions() + // TODO: remove the DemoLoggerProvider or convert it to something more permanent + var loggerFactory = LoggerFactory.Create(builder => + { + builder.SetMinimumLevel((options.Verbose, options.Quiet) switch { - MinLevel = options.Verbose ? LogLevel.Trace : LogLevel.Information + (_, true) => LogLevel.Error, + (true, _) => LogLevel.Trace, + _ => LogLevel.Information }); - - if (!options.Quiet) - { - loggerFactory.AddProvider(new DemoLoggerProvider()); - } + builder.AddProvider(new DemoLoggerProvider()); + }); var logger = loggerFactory.CreateLogger(); try @@ -106,15 +104,23 @@ static int HandleCommand(string assembliesPath, string sourcePath, string[]? ref try { + var artifactsDirs = options.AssembliesPaths.Select(path => new DirectoryInfo(path)); + using (logger.BeginScope("Rebuild Search Paths")) + { + foreach (var artifactsDir in artifactsDirs) + { + logger.LogInformation($@"""{artifactsDir.FullName}"""); + } + } + var sourceResolver = new LocalSourceResolver(options, loggerFactory); var referenceResolver = new LocalReferenceResolver(options, loggerFactory); var buildConstructor = new BuildConstructor(referenceResolver, sourceResolver, logger); - var artifactsDir = new DirectoryInfo(options.AssembliesPath); - - var filesToValidate = artifactsDir.EnumerateFiles("*.exe", SearchOption.AllDirectories) - .Concat(artifactsDir.EnumerateFiles("*.dll", SearchOption.AllDirectories)) + var filesToValidate = artifactsDirs.SelectMany(dir => + dir.EnumerateFiles("*.exe", SearchOption.AllDirectories) + .Concat(dir.EnumerateFiles("*.dll", SearchOption.AllDirectories))) .Distinct(FileNameEqualityComparer.Instance); var success = ValidateFiles(filesToValidate, buildConstructor, logger, options); @@ -156,6 +162,16 @@ private static bool ValidateFiles(IEnumerable originalBinaries, BuildC } } + using (logger.BeginScope("Rebuilds with configuration issues")) + { + foreach (var diff in assembliesCompiled.Where(a => a.AreEqual is null && a.Diagnostics.IsDefaultOrEmpty)) + { + logger.LogError($"{diff.OriginalPath} was missing required metadata for rebuilding. Was it built with a recent enough compiler with the required settings?"); + // dependencies which don't have the required metadata have a way of sneaking into the obj folder. + // for now, let's not let presence of these assemblies cause the rebuild to fail. + } + } + using (logger.BeginScope("Rebuilds with output differences")) { foreach (var diff in assembliesCompiled.Where(a => a.AreEqual == false)) @@ -165,10 +181,9 @@ private static bool ValidateFiles(IEnumerable originalBinaries, BuildC success = false; } } - using (logger.BeginScope("Rebuilds with compilation errors")) { - foreach (var diff in assembliesCompiled.Where(a => a.AreEqual == null)) + foreach (var diff in assembliesCompiled.Where(a => !a.Diagnostics.IsDefaultOrEmpty)) { logger.LogError($"{diff.OriginalPath} had {diff.Diagnostics.Length} diagnostics."); success = false; @@ -203,7 +218,7 @@ private static bool ValidateFiles(IEnumerable originalBinaries, BuildC if (!pdbOpened || pdbReaderProvider is null) { logger.LogError($"Could not find pdb for {originalBinary.FullName}"); - return null; + return CompilationDiff.CreatePlaceholder(originalBinary); } using var _ = logger.BeginScope($"Verifying {originalBinary.FullName} with pdb {pdbPath ?? "[embedded]"}"); @@ -213,7 +228,11 @@ private static bool ValidateFiles(IEnumerable originalBinaries, BuildC var compilation = buildConstructor.CreateCompilation( optionsReader, - Path.GetFileNameWithoutExtension(originalBinary.Name)); + originalBinary.Name); + if (compilation is null) + { + return CompilationDiff.CreatePlaceholder(originalBinary); + } var compilationDiff = CompilationDiff.Create(originalBinary, optionsReader, compilation, getDebugEntryPoint(), logger, options); return compilationDiff; diff --git a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj index e7b8a64bd65eb..6e71d77ce5c91 100644 --- a/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj +++ b/src/Tools/ExternalAccess/Razor/Microsoft.CodeAnalysis.ExternalAccess.Razor.csproj @@ -25,6 +25,7 @@ + diff --git a/src/Tools/ExternalAccess/Razor/RazorCSharpBreakpointResolver.cs b/src/Tools/ExternalAccess/Razor/RazorBreakpointSpans.cs similarity index 100% rename from src/Tools/ExternalAccess/Razor/RazorCSharpBreakpointResolver.cs rename to src/Tools/ExternalAccess/Razor/RazorBreakpointSpans.cs diff --git a/src/Tools/ExternalAccess/Razor/RazorCSharpProximityExpressionResolver.cs b/src/Tools/ExternalAccess/Razor/RazorCSharpProximityExpressionResolverService.cs similarity index 100% rename from src/Tools/ExternalAccess/Razor/RazorCSharpProximityExpressionResolver.cs rename to src/Tools/ExternalAccess/Razor/RazorCSharpProximityExpressionResolverService.cs diff --git a/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj b/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj index 71c7bae403f30..96f5f69e160da 100644 --- a/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj +++ b/src/Tools/IdeCoreBenchmarks/IdeCoreBenchmarks.csproj @@ -5,7 +5,7 @@ Exe - netcoreapp3.1;net472 + net5.0;netcoreapp3.1;net472 false AnyCPU diff --git a/src/Tools/IdeCoreBenchmarks/SegmentedArrayBenchmarks.cs b/src/Tools/IdeCoreBenchmarks/SegmentedArrayBenchmarks.cs new file mode 100644 index 0000000000000..a35e4cca6e9af --- /dev/null +++ b/src/Tools/IdeCoreBenchmarks/SegmentedArrayBenchmarks.cs @@ -0,0 +1,75 @@ +// 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 BenchmarkDotNet.Attributes; +using Microsoft.CodeAnalysis.Collections; + +namespace IdeCoreBenchmarks +{ + [DisassemblyDiagnoser] + public class SegmentedArrayBenchmarks + { + private int[] _values = null!; + private object?[] _valuesObject = null!; + + private SegmentedArray _segmentedValues; + private SegmentedArray _segmentedValuesObject; + + [Params(100000)] + public int Count { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + _values = new int[Count]; + _valuesObject = new object?[Count]; + _segmentedValues = new SegmentedArray(Count); + _segmentedValuesObject = new SegmentedArray(Count); + } + + [Benchmark(Description = "int[]", Baseline = true)] + public void ShiftAllArray() + { + for (var i = 0; i < _values.Length - 1; i++) + { + _values[i] = _values[i + 1]; + } + + _values[^1] = 0; + } + + [Benchmark(Description = "object[]")] + public void ShiftAllArrayObject() + { + for (var i = 0; i < _valuesObject.Length - 1; i++) + { + _valuesObject[i] = _valuesObject[i + 1]; + } + + _valuesObject[^1] = null; + } + + [Benchmark(Description = "SegmentedArray")] + public void ShiftAllSegmented() + { + for (var i = 0; i < _segmentedValues.Length - 1; i++) + { + _segmentedValues[i] = _segmentedValues[i + 1]; + } + + _segmentedValues[^1] = 0; + } + + [Benchmark(Description = "SegmentedArray")] + public void ShiftAllSegmentedObject() + { + for (var i = 0; i < _segmentedValuesObject.Length - 1; i++) + { + _segmentedValuesObject[i] = _segmentedValuesObject[i + 1]; + } + + _segmentedValuesObject[^1] = null; + } + } +} diff --git a/src/Tools/ManifestGenerator/README.md b/src/Tools/ManifestGenerator/README.md index ef0eaaf06037e..d796d86bfb342 100644 --- a/src/Tools/ManifestGenerator/README.md +++ b/src/Tools/ManifestGenerator/README.md @@ -1,4 +1,3 @@ # dotnet-roslyn-manifest-generator -https://github.com/dotnet/roslyn/blob/efd69176d6ad7f9dbe1d87525201760a6c44e7a0/docs/compilers/terrapin.md#artifacts-manifest-file - +[Terrapin doc](../../../docs/compilers/terrapin.md#artifacts-manifest-file) diff --git a/src/Tools/Source/RunTests/RunTests.csproj b/src/Tools/Source/RunTests/RunTests.csproj index 69d50f50eab30..ad17ab0a98755 100644 --- a/src/Tools/Source/RunTests/RunTests.csproj +++ b/src/Tools/Source/RunTests/RunTests.csproj @@ -7,7 +7,6 @@ true false false - true diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs index c7528173a19b1..4c54f7cca1358 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/AbstractPersistentStorageTests.cs @@ -100,7 +100,7 @@ public async Task TestNullFilePaths() var streamName = "stream"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); var project = solution.Projects.First(); var document = project.Documents.First(); Assert.False(await storage.WriteStreamAsync(project, streamName, EncodeString(""))); @@ -118,13 +118,13 @@ public async Task PersistentService_Solution_WriteReadDifferentInstances(Size si var streamName1 = "PersistentService_Solution_WriteReadDifferentInstances1"; var streamName2 = "PersistentService_Solution_WriteReadDifferentInstances2"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)))); Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum)))); @@ -139,7 +139,7 @@ public async Task PersistentService_Solution_WriteReadReopenSolution(Size size, var streamName1 = "PersistentService_Solution_WriteReadReopenSolution1"; var streamName2 = "PersistentService_Solution_WriteReadReopenSolution2"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); @@ -147,7 +147,7 @@ public async Task PersistentService_Solution_WriteReadReopenSolution(Size size, solution = CreateOrOpenSolution(); - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.Equal(GetData1(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum)))); Assert.Equal(GetData2(size), ReadStringToEnd(await storage.ReadStreamAsync(streamName2, GetChecksum2(withChecksum)))); @@ -162,7 +162,7 @@ public async Task PersistentService_Solution_WriteReadSameInstance(Size size, bo var streamName1 = "PersistentService_Solution_WriteReadSameInstance1"; var streamName2 = "PersistentService_Solution_WriteReadSameInstance2"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); Assert.True(await storage.WriteStreamAsync(streamName2, EncodeString(GetData2(size)), GetChecksum2(withChecksum))); @@ -178,7 +178,7 @@ public async Task PersistentService_Project_WriteReadSameInstance(Size size, boo var streamName1 = "PersistentService_Project_WriteReadSameInstance1"; var streamName2 = "PersistentService_Project_WriteReadSameInstance2"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); var project = solution.Projects.Single(); Assert.True(await storage.WriteStreamAsync(project, streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); @@ -196,7 +196,7 @@ public async Task PersistentService_Document_WriteReadSameInstance(Size size, bo var streamName1 = "PersistentService_Document_WriteReadSameInstance1"; var streamName2 = "PersistentService_Document_WriteReadSameInstance2"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); var document = solution.Projects.Single().Documents.Single(); Assert.True(await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); @@ -213,7 +213,7 @@ public async Task PersistentService_Solution_SimultaneousWrites() var streamName1 = "PersistentService_Solution_SimultaneousWrites1"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); DoSimultaneousWrites(s => storage.WriteStreamAsync(streamName1, EncodeString(s))); var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(streamName1))); Assert.True(value >= 0); @@ -227,7 +227,7 @@ public async Task PersistentService_Project_SimultaneousWrites() var streamName1 = "PersistentService_Project_SimultaneousWrites1"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); DoSimultaneousWrites(s => storage.WriteStreamAsync(solution.Projects.Single(), streamName1, EncodeString(s))); var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single(), streamName1))); Assert.True(value >= 0); @@ -241,7 +241,7 @@ public async Task PersistentService_Document_SimultaneousWrites() var streamName1 = "PersistentService_Document_SimultaneousWrites1"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); DoSimultaneousWrites(s => storage.WriteStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, EncodeString(s))); var value = int.Parse(ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single().Documents.Single(), streamName1))); Assert.True(value >= 0); @@ -273,7 +273,7 @@ public async Task PersistentService_Solution_SimultaneousReads(Size size, bool w var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_Solution_SimultaneousReads1"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(streamName1, GetChecksum1(withChecksum))), GetData1(size)); } @@ -285,7 +285,7 @@ public async Task PersistentService_Project_SimultaneousReads(Size size, bool wi var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_Project_SimultaneousReads1"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(solution.Projects.Single(), streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single(), streamName1, GetChecksum1(withChecksum))), GetData1(size)); } @@ -299,7 +299,7 @@ public async Task PersistentService_Document_SimultaneousReads(Size size, bool w var solution = CreateOrOpenSolution(); var streamName1 = "PersistentService_Document_SimultaneousReads1"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); Assert.True(await storage.WriteStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, EncodeString(GetData1(size)), GetChecksum1(withChecksum))); DoSimultaneousReads(async () => ReadStringToEnd(await storage.ReadStreamAsync(solution.Projects.Single().Documents.Single(), streamName1, GetChecksum1(withChecksum))), GetData1(size)); } @@ -311,7 +311,7 @@ public async Task TestReadChecksumReturnsNullWhenNeverWritten() var streamName1 = "TestReadChecksumReturnsNullWhenNeverWritten"; - using var storage = await GetStorageAsync(solution); + await using var storage = await GetStorageAsync(solution); Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } @@ -322,12 +322,12 @@ public async Task TestCanReadWithNullChecksumSomethingWrittenWithNonNullChecksum var streamName1 = "TestCanReadWithNullChecksumSomethingWrittenWithNonNullChecksum"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(Size.Small)), s_checksum1)); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(streamName1, checksum: null))); } @@ -340,12 +340,12 @@ public async Task TestCannotReadWithMismatchedChecksums() var streamName1 = "TestCannotReadWithMismatchedChecksums"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(Size.Small)), s_checksum1)); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.Null(await storage.ReadStreamAsync(streamName1, s_checksum2)); } @@ -358,12 +358,12 @@ public async Task TestCannotReadChecksumIfWriteDidNotIncludeChecksum() var streamName1 = "TestCannotReadChecksumIfWriteDidNotIncludeChecksum"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(Size.Small)), checksum: null)); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } @@ -376,12 +376,12 @@ public async Task TestReadChecksumProducesWrittenChecksum() var streamName1 = "TestReadChecksumProducesWrittenChecksum"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1)); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } @@ -394,13 +394,13 @@ public async Task TestReadChecksumProducesLastWrittenChecksum1() var streamName1 = "TestReadChecksumProducesLastWrittenChecksum1"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1)); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(Size.Small)), checksum: null)); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.False(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } @@ -413,13 +413,13 @@ public async Task TestReadChecksumProducesLastWrittenChecksum2() var streamName1 = "TestReadChecksumProducesLastWrittenChecksum2"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(Size.Small)), checksum: null)); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1)); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum1)); } @@ -432,13 +432,13 @@ public async Task TestReadChecksumProducesLastWrittenChecksum3() var streamName1 = "TestReadChecksumProducesLastWrittenChecksum3"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1)); Assert.True(await storage.WriteStreamAsync(streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum2)); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(streamName1, s_checksum2)); } @@ -452,12 +452,12 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKey() var streamName1 = "stream"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -472,12 +472,12 @@ public async Task TestOpenWithSolutionKeyReadWithDocument() var streamName1 = "stream"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -492,12 +492,12 @@ public async Task TestOpenWithSolutionReadWithDocumentKey() var streamName1 = "stream"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -512,12 +512,12 @@ public async Task TestOpenWithSolutionReadWithDocument() var streamName1 = "stream"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -532,12 +532,12 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument1() var streamName1 = "stream"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -555,12 +555,12 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument2() var streamName1 = "stream"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -578,12 +578,12 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument1() var streamName1 = "stream"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -601,12 +601,12 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument2() var streamName1 = "stream"; - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -624,12 +624,12 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKey_WriteWithSolutionKe var streamName1 = "stream"; - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -644,12 +644,12 @@ public async Task TestOpenWithSolutionKeyReadWithDocument_WriteWithSolutionKey() var streamName1 = "stream"; - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -664,12 +664,12 @@ public async Task TestOpenWithSolutionReadWithDocumentKey_WriteWithSolutionKey() var streamName1 = "stream"; - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -684,12 +684,12 @@ public async Task TestOpenWithSolutionReadWithDocument_WriteWithSolutionKey() var streamName1 = "stream"; - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -704,12 +704,12 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument1_WriteWithS var streamName1 = "stream"; - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -727,12 +727,12 @@ public async Task TestOpenWithSolutionReadWithDocumentKeyAndDocument2_WriteWithS var streamName1 = "stream"; - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageAsync(solution)) + await using (var storage = await GetStorageAsync(solution)) { Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); @@ -750,12 +750,12 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument1_WriteWi var streamName1 = "stream"; - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { Assert.True(await storage.ChecksumMatchesAsync(DocumentKey.ToDocumentKey(document), streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(DocumentKey.ToDocumentKey(document), streamName1))); @@ -773,12 +773,12 @@ public async Task TestOpenWithSolutionKeyReadWithDocumentKeyAndDocument2_WriteWi var streamName1 = "stream"; - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { await storage.WriteStreamAsync(document, streamName1, EncodeString(GetData1(Size.Small)), checksum: s_checksum1); } - using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) + await using (var storage = await GetStorageFromKeyAsync(solution.Workspace, SolutionKey.ToSolutionKey(solution))) { Assert.True(await storage.ChecksumMatchesAsync(document, streamName1, s_checksum1)); Assert.Equal(GetData1(Size.Small), ReadStringToEnd(await storage.ReadStreamAsync(document, streamName1))); diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs index 5bb184aa6c720..f05948bfd630a 100644 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs +++ b/src/VisualStudio/CSharp/Test/PersistentStorage/SQLiteV2PersistentStorageTests.cs @@ -40,7 +40,7 @@ public async Task TestCrashInNewConnection() // Because instantiating the connection will fail, we will not get back // a working persistent storage. - using (var storage = await GetStorageAsync(solution, faultInjector)) + await using (var storage = await GetStorageAsync(solution, faultInjector)) using (var memStream = new MemoryStream()) using (var streamWriter = new StreamWriter(memStream)) { diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs index 106c01147f292..0410a79eed73e 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs @@ -30,7 +30,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Lsp [Export(typeof(ILanguageClient))] internal class RazorInProcLanguageClient : AbstractInProcLanguageClient { - public const string ClientName = "RazorCSharp"; + public const string ClientName = ProtocolConstants.RazorCSharp; private readonly IGlobalOptionService _globalOptionService; private readonly DefaultCapabilitiesProvider _defaultCapabilitiesProvider; diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index d3b5d3085a399..af8f1c69597d6 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -1729,6 +1729,13 @@ private bool CanConvertMetadataReferenceToProjectReference_NoLock(ProjectId proj { Debug.Assert(Monitor.IsEntered(_gate)); + // We can never make a project reference ourselves. This isn't a meaningful scenario, but if somebody does this by accident + // we do want to throw exceptions. + if (projectIdWithMetadataReference == referencedProjectId) + { + return false; + } + // PERF: call GetProjectState instead of GetProject, otherwise creating a new project might force all // Project instances to get created. var projectWithMetadataReference = CurrentSolution.GetProjectState(projectIdWithMetadataReference); diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/RemoveUnusedReferencesDialog.xaml.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/RemoveUnusedReferencesDialog.xaml.cs index c21a7d181ddee..58e9581e21357 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/RemoveUnusedReferencesDialog.xaml.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/RemoveUnusedReferencesDialog.xaml.cs @@ -31,13 +31,13 @@ public RemoveUnusedReferencesDialog(UnusedReferencesTableProvider tableProvider) InitializeComponent(); } - public bool? ShowModal(Project project, ImmutableArray referenceUpdates) + public bool? ShowModal(Solution solution, string projectFilePath, ImmutableArray referenceUpdates) { bool? result = null; try { - _tableProvider.AddTableData(project, referenceUpdates); + _tableProvider.AddTableData(solution, projectFilePath, referenceUpdates); using var tableControl = _tableProvider.CreateTableControl(); TablePanel.Child = tableControl.Control; diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.ColumnDefinitions.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.ColumnDefinitions.cs index 2af2b88da268e..60de86a261e39 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.ColumnDefinitions.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.ColumnDefinitions.cs @@ -25,11 +25,8 @@ internal partial class UnusedReferencesTableProvider { internal class ReferenceImageMonikers { - // These GUIDs and IDs are defined in src\Microsoft.VisualStudio.ProjectSystem.Managed.VS\ManagedImages.imagemanifest - private static readonly Guid s_manifestGuid = new("{259567C1-AA6B-46BF-811C-C145DD9F3B48}"); - // Change this to use KnownMonikers.NuGetNoColor once we are able to move to Microsoft.VisualStudio.ImageCatalog v16.9 - public static ImageMoniker Package => new() { Guid = s_manifestGuid, Id = 9 }; + public static ImageMoniker Package => new() { Guid = KnownImageIds.ImageCatalogGuid, Id = 3902 }; public static ImageMoniker Project => KnownMonikers.Application; public static ImageMoniker Assembly => KnownMonikers.Reference; } @@ -104,6 +101,17 @@ internal static FrameworkElement CreateGridElement(ImageMoniker imageMoniker, st return stackPanel; } + private static ImageMoniker GetReferenceTypeImageMoniker(ReferenceType referenceType) + { + return referenceType switch + { + ReferenceType.Package => ReferenceImageMonikers.Package, + ReferenceType.Project => ReferenceImageMonikers.Project, + ReferenceType.Assembly => ReferenceImageMonikers.Assembly, + _ => throw ExceptionUtilities.UnexpectedValue(referenceType) + }; + } + [Export(typeof(ITableColumnDefinition))] [Name(UnusedReferencesColumnDefinitions.SolutionName)] internal class SolutionNameColumnDefinition : TableColumnDefinitionBase @@ -128,6 +136,11 @@ public override bool TryCreateColumnContent(ITableEntryHandle entry, bool single return false; } + public override bool TryCreateStringContent(ITableEntryHandle entry, bool truncatedText, bool singleColumnView, out string content) + { + return entry.TryGetValue(UnusedReferencesTableKeyNames.SolutionName, out content); + } + public override IEntryBucket? CreateBucketForEntry(ITableEntryHandle entry) { return entry.TryGetValue(UnusedReferencesTableKeyNames.SolutionName, out string name) @@ -160,6 +173,11 @@ public override bool TryCreateColumnContent(ITableEntryHandle entry, bool single return false; } + public override bool TryCreateStringContent(ITableEntryHandle entry, bool truncatedText, bool singleColumnView, out string content) + { + return entry.TryGetValue(UnusedReferencesTableKeyNames.ProjectName, out content); + } + public override IEntryBucket? CreateBucketForEntry(ITableEntryHandle entry) { return entry.TryGetValue(UnusedReferencesTableKeyNames.ProjectName, out string name) @@ -191,7 +209,7 @@ public override bool TryCreateColumnContent(ITableEntryHandle entry, bool single { if (entry.TryGetValue(UnusedReferencesTableKeyNames.ReferenceType, out var referenceType)) { - content = CreateGridElement(GetImageMoniker(referenceType), GetText(referenceType), isBold: false); + content = CreateGridElement(GetReferenceTypeImageMoniker(referenceType), GetText(referenceType), isBold: false); return true; } @@ -199,22 +217,19 @@ public override bool TryCreateColumnContent(ITableEntryHandle entry, bool single return false; } - public override IEntryBucket? CreateBucketForEntry(ITableEntryHandle entry) + public override bool TryCreateStringContent(ITableEntryHandle entry, bool truncatedText, bool singleColumnView, out string? content) { - return entry.TryGetValue(UnusedReferencesTableKeyNames.ReferenceType, out var referenceType) - ? new ImageEntryBucket(GetImageMoniker(referenceType), GetText(referenceType)) + content = entry.TryGetValue(UnusedReferencesTableKeyNames.ReferenceType, out var referenceType) + ? GetText(referenceType) : null; + return content != null; } - private static ImageMoniker GetImageMoniker(ReferenceType referenceType) + public override IEntryBucket? CreateBucketForEntry(ITableEntryHandle entry) { - return referenceType switch - { - ReferenceType.Package => ReferenceImageMonikers.Package, - ReferenceType.Project => ReferenceImageMonikers.Project, - ReferenceType.Assembly => ReferenceImageMonikers.Assembly, - _ => throw ExceptionUtilities.UnexpectedValue(referenceType) - }; + return entry.TryGetValue(UnusedReferencesTableKeyNames.ReferenceType, out var referenceType) + ? new ImageEntryBucket(GetReferenceTypeImageMoniker(referenceType), GetText(referenceType)) + : null; } private static string GetText(ReferenceType referenceType) @@ -250,16 +265,15 @@ public override bool TryCreateColumnContent(ITableEntryHandle entry, bool single return true; } + public override bool TryCreateStringContent(ITableEntryHandle entry, bool truncatedText, bool singleColumnView, out string content) + { + return entry.TryGetValue(UnusedReferencesTableKeyNames.ReferenceName, out content); + } + private static ImageMoniker GetImageMoniker(ITableEntryHandle entry) { return entry.TryGetValue(UnusedReferencesTableKeyNames.ReferenceType, out ReferenceType referenceType) - ? referenceType switch - { - ReferenceType.Package => KnownMonikers.PackageReference, - ReferenceType.Project => KnownMonikers.Library, - ReferenceType.Assembly => KnownMonikers.Reference, - _ => throw ExceptionUtilities.UnexpectedValue(referenceType) - } + ? GetReferenceTypeImageMoniker(referenceType) : default; } @@ -284,6 +298,7 @@ public UpdateActionColumnDefinition() public override string Name => UnusedReferencesColumnDefinitions.UpdateAction; public override string DisplayName => ServicesVSResources.Action; public override bool IsFilterable => false; + public override bool IsSortable => false; public override double MinWidth => 100; public override bool TryCreateColumnContent(ITableEntryHandle entry, bool singleColumnView, out FrameworkElement? content) diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.DataSource.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.DataSource.cs index d3858713c6c49..c33796e4ea9d1 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.DataSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.DataSource.cs @@ -30,9 +30,10 @@ public IDisposable Subscribe(ITableDataSink sink) return new SinkManager(this, sink); } - public void AddTableData(Project project, ImmutableArray referenceUpdates) + public void AddTableData(Solution solution, string projectFilePath, ImmutableArray referenceUpdates) { - var solutionName = Path.GetFileName(project.Solution.FilePath); + var solutionName = Path.GetFileName(solution.FilePath); + var project = solution.Projects.First(project => projectFilePath.Equals(project.FilePath, StringComparison.OrdinalIgnoreCase)); var entries = referenceUpdates .Select(update => new UnusedReferencesEntry(solutionName, project.Name, project.Language, update)) .ToImmutableArray(); diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs index 48b33a1784985..0808fb55bea7b 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/Dialog/UnusedReferencesTableProvider.cs @@ -52,16 +52,16 @@ static ImmutableArray BuildColumnStates() { return ImmutableArray.Create( new ColumnState2(UnusedReferencesColumnDefinitions.SolutionName, isVisible: false, width: 200, sortPriority: 0, descendingSort: false, groupingPriority: 1), - new ColumnState2(UnusedReferencesColumnDefinitions.ProjectName, isVisible: false, width: 200, sortPriority: 0, descendingSort: false, groupingPriority: 2), - new ColumnState2(UnusedReferencesColumnDefinitions.ReferenceType, isVisible: false, width: 200, sortPriority: 0, descendingSort: false, groupingPriority: 3), - new ColumnState(UnusedReferencesColumnDefinitions.ReferenceName, isVisible: true, width: 300, sortPriority: 0, descendingSort: false), - new ColumnState(UnusedReferencesColumnDefinitions.UpdateAction, isVisible: true, width: 100, sortPriority: 0, descendingSort: false)); + new ColumnState2(UnusedReferencesColumnDefinitions.ProjectName, isVisible: false, width: 200, sortPriority: 1, descendingSort: false, groupingPriority: 2), + new ColumnState2(UnusedReferencesColumnDefinitions.ReferenceType, isVisible: false, width: 200, sortPriority: 2, descendingSort: false, groupingPriority: 3), + new ColumnState(UnusedReferencesColumnDefinitions.ReferenceName, isVisible: true, width: 300, sortPriority: 3, descendingSort: false), + new ColumnState(UnusedReferencesColumnDefinitions.UpdateAction, isVisible: true, width: 100, sortPriority: 4, descendingSort: false)); } } - public void AddTableData(Project project, ImmutableArray referenceUpdates) + public void AddTableData(Solution solution, string projectFilePath, ImmutableArray referenceUpdates) { - _dataSource.AddTableData(project, referenceUpdates); + _dataSource.AddTableData(solution, projectFilePath, referenceUpdates); } public void ClearTableData() diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs index ad64431a8c230..f88ec084c9b02 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/ProjectAssets/ProjectAssetsReader.cs @@ -21,8 +21,7 @@ internal static class ProjectAssetsReader public static ImmutableArray ReadReferences( ImmutableArray projectReferences, - string projectAssetsFilePath, - string targetFrameworkMoniker) + string projectAssetsFilePath) { if (!File.Exists(projectAssetsFilePath)) { @@ -48,7 +47,13 @@ public static ImmutableArray ReadReferences( } if (projectAssets.Targets is null || - !projectAssets.Targets.TryGetValue(targetFrameworkMoniker, out var target)) + projectAssets.Targets.Count == 0) + { + return ImmutableArray.Empty; + } + + if (projectAssets.Libraries is null || + projectAssets.Libraries.Count == 0) { return ImmutableArray.Empty; } @@ -60,7 +65,7 @@ public static ImmutableArray ReadReferences( autoReferences ??= ImmutableHashSet.Empty; var references = projectReferences - .Select(projectReference => BuildReference(projectAssets, target, projectReference, autoReferences)) + .Select(projectReference => BuildReference(projectAssets, projectReference, autoReferences)) .WhereNotNull() .ToImmutableArray(); @@ -69,7 +74,6 @@ public static ImmutableArray ReadReferences( private static ReferenceInfo? BuildReference( ProjectAssetsFile projectAssets, - Dictionary target, ReferenceInfo referenceInfo, ImmutableHashSet autoReferences) { @@ -82,61 +86,65 @@ public static ImmutableArray ReadReferences( return null; } - return BuildReference(projectAssets, target, referenceName, referenceInfo.TreatAsUsed); + return BuildReference(projectAssets, referenceName, referenceInfo.TreatAsUsed); } private static ReferenceInfo? BuildReference( ProjectAssetsFile projectAssets, - Dictionary target, - string dependency, + string referenceName, bool treatAsUsed) { - var key = target.Keys.FirstOrDefault(library => library.Split('/')[0] == dependency); - if (key is null) + var dependencyNames = new HashSet(); + var compilationAssemblies = ImmutableArray.CreateBuilder(); + var referenceType = ReferenceType.Unknown; + + var packagesPath = projectAssets.Project?.Restore?.PackagesPath ?? string.Empty; + + RoslynDebug.AssertNotNull(projectAssets.Targets); + RoslynDebug.AssertNotNull(projectAssets.Libraries); + + foreach (var target in projectAssets.Targets.Values) { - return null; + var key = target.Keys.FirstOrDefault(library => library.Split('/')[0] == referenceName); + if (key is null || + !projectAssets.Libraries.TryGetValue(key, out var library)) + { + continue; + } + + var targetLibrary = target[key]; + + referenceType = targetLibrary.Type switch + { + "package" => ReferenceType.Package, + "project" => ReferenceType.Project, + _ => ReferenceType.Assembly + }; + + if (targetLibrary.Dependencies != null) + { + dependencyNames.AddRange(targetLibrary.Dependencies.Keys); + } + + if (targetLibrary.Compile != null) + { + compilationAssemblies.AddRange(targetLibrary.Compile.Keys + .Where(assemblyPath => !assemblyPath.EndsWith(NuGetEmptyFileName)) + .Select(assemblyPath => Path.GetFullPath(Path.Combine(packagesPath, library.Path, assemblyPath)))); + } } - return BuildReference(projectAssets, target, dependency, treatAsUsed, key, target[key]); - } - - private static ReferenceInfo? BuildReference( - ProjectAssetsFile projectAssets, - Dictionary target, - string referenceName, - bool treatAsUsed, - string key, - ProjectAssetsTargetLibrary targetLibrary) - { - if (projectAssets.Libraries is null || - !projectAssets.Libraries.TryGetValue(key, out var library)) + if (referenceType == ReferenceType.Unknown) { return null; } - var type = targetLibrary.Type switch - { - "package" => ReferenceType.Package, - "project" => ReferenceType.Project, - _ => ReferenceType.Assembly - }; - - var dependencies = targetLibrary.Dependencies != null - ? targetLibrary.Dependencies.Keys - .Select(dependency => BuildReference(projectAssets, target, dependency, treatAsUsed: false)) - .WhereNotNull() - .ToImmutableArray() - : ImmutableArray.Empty; + var dependencies = dependencyNames + .Select(dependency => BuildReference(projectAssets, dependency, treatAsUsed: false)) + .WhereNotNull() + .ToImmutableArray(); - var packagesPath = projectAssets.Project?.Restore?.PackagesPath ?? string.Empty; - var compilationAssemblies = targetLibrary.Compile != null - ? targetLibrary.Compile.Keys - .Where(assemblyPath => !assemblyPath.EndsWith(NuGetEmptyFileName)) - .Select(assemblyPath => Path.GetFullPath(Path.Combine(packagesPath, library.Path, assemblyPath))) - .ToImmutableArray() - : ImmutableArray.Empty; - - return new ReferenceInfo(type, referenceName, treatAsUsed, compilationAssemblies, dependencies); + return new ReferenceInfo(referenceType, referenceName, treatAsUsed, compilationAssemblies.ToImmutable(), dependencies); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs index 011fab72868b8..5791ef40449ad 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs @@ -13,7 +13,6 @@ using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Experiments; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.UnusedReferences; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.UnusedReferences.Dialog; @@ -107,11 +106,12 @@ private void OnRemoveUnusedReferencesForSelectedProject(object sender, EventArgs { if (VisualStudioCommandHandlerHelpers.TryGetSelectedProjectHierarchy(_serviceProvider, out var hierarchy)) { - Project? project = null; + Solution? solution = null; + string? projectFilePath = null; ImmutableArray referenceUpdates = default; var status = _threadOperationExecutor.Execute(ServicesVSResources.Remove_Unused_References, ServicesVSResources.Analyzing_project_references, allowCancellation: true, showProgress: true, (operationContext) => { - (project, referenceUpdates) = GetUnusedReferencesForProjectHierarchy(hierarchy, operationContext.UserCancellationToken); + (solution, projectFilePath, referenceUpdates) = GetUnusedReferencesForProjectHierarchy(hierarchy, operationContext.UserCancellationToken); }); if (status == UIThreadOperationStatus.Canceled) @@ -119,7 +119,8 @@ private void OnRemoveUnusedReferencesForSelectedProject(object sender, EventArgs return; } - if (project is null || + if (solution is null || + projectFilePath is not string { Length: > 0 } || referenceUpdates.IsEmpty) { MessageDialog.Show(ServicesVSResources.Remove_Unused_References, ServicesVSResources.No_unused_references_were_found, MessageDialogCommandSet.Ok); @@ -127,7 +128,7 @@ private void OnRemoveUnusedReferencesForSelectedProject(object sender, EventArgs } var dialog = _unusedReferenceDialogProvider.CreateDialog(); - if (dialog.ShowModal(project, referenceUpdates) == false) + if (dialog.ShowModal(solution, projectFilePath, referenceUpdates) == false) { return; } @@ -153,48 +154,43 @@ private void OnRemoveUnusedReferencesForSelectedProject(object sender, EventArgs _threadOperationExecutor.Execute(ServicesVSResources.Remove_Unused_References, ServicesVSResources.Updating_project_references, allowCancellation: false, showProgress: true, (operationContext) => { - ApplyUnusedReferenceUpdates(project, referenceChanges, CancellationToken.None); + ApplyUnusedReferenceUpdates(solution, projectFilePath, referenceChanges, CancellationToken.None); }); } return; } - private (Project?, ImmutableArray) GetUnusedReferencesForProjectHierarchy(IVsHierarchy projectHierarchy, CancellationToken cancellationToken) + private (Solution?, string?, ImmutableArray) GetUnusedReferencesForProjectHierarchy( + IVsHierarchy projectHierarchy, + CancellationToken cancellationToken) { - if (!TryGetPropertyValue(projectHierarchy, ProjectAssetsFilePropertyName, out var projectAssetsFile) || - !projectHierarchy.TryGetTargetFrameworkMoniker((uint)VSConstants.VSITEMID.Root, out var targetFrameworkMoniker)) + if (!TryGetPropertyValue(projectHierarchy, ProjectAssetsFilePropertyName, out var projectAssetsFile)) { - return (null, ImmutableArray.Empty); + return (null, null, ImmutableArray.Empty); } - var projectMap = _workspace.Services.GetRequiredService(); - var projectHierarchyItem = _vsHierarchyItemManager.GetHierarchyItem(projectHierarchy, VSConstants.VSITEMID_ROOT); - - if (!projectMap.TryGetProjectId(projectHierarchyItem, targetFrameworkMoniker, out var projectId)) + var projectFilePath = projectHierarchy.TryGetProjectFilePath(); + if (string.IsNullOrEmpty(projectFilePath)) { - return (null, ImmutableArray.Empty); + return (null, null, ImmutableArray.Empty); } - var project = _workspace.CurrentSolution.GetProject(projectId); - if (project is null) - { - return (null, ImmutableArray.Empty); - } + var solution = _workspace.CurrentSolution; - var unusedReferences = GetUnusedReferencesForProject(project, projectAssetsFile, targetFrameworkMoniker, cancellationToken); + var unusedReferences = GetUnusedReferencesForProject(solution, projectFilePath!, projectAssetsFile, cancellationToken); - return (project, unusedReferences); + return (solution, projectFilePath, unusedReferences); } - private ImmutableArray GetUnusedReferencesForProject(Project project, string projectAssetsFile, string targetFrameworkMoniker, CancellationToken cancellationToken) + private ImmutableArray GetUnusedReferencesForProject(Solution solution, string projectFilePath, string projectAssetsFile, CancellationToken cancellationToken) { ImmutableArray unusedReferences = ThreadHelper.JoinableTaskFactory.Run(async () => { - var projectReferences = await _lazyReferenceCleanupService.Value.GetProjectReferencesAsync(project.FilePath!, cancellationToken).ConfigureAwait(true); - var references = ProjectAssetsReader.ReadReferences(projectReferences, projectAssetsFile, targetFrameworkMoniker); + var projectReferences = await _lazyReferenceCleanupService.Value.GetProjectReferencesAsync(projectFilePath, cancellationToken).ConfigureAwait(true); + var references = ProjectAssetsReader.ReadReferences(projectReferences, projectAssetsFile); - return await UnusedReferencesRemover.GetUnusedReferencesAsync(project, references, cancellationToken).ConfigureAwait(true); + return await UnusedReferencesRemover.GetUnusedReferencesAsync(solution, projectFilePath, references, cancellationToken).ConfigureAwait(true); }); var referenceUpdates = unusedReferences @@ -204,10 +200,10 @@ private ImmutableArray GetUnusedReferencesForProject(Project pr return referenceUpdates; } - private void ApplyUnusedReferenceUpdates(Project project, ImmutableArray referenceUpdates, CancellationToken cancellationToken) + private void ApplyUnusedReferenceUpdates(Solution solution, string projectFilePath, ImmutableArray referenceUpdates, CancellationToken cancellationToken) { ThreadHelper.JoinableTaskFactory.Run( - () => UnusedReferencesRemover.UpdateReferencesAsync(project, referenceUpdates, cancellationToken)); + () => UnusedReferencesRemover.UpdateReferencesAsync(solution, projectFilePath, referenceUpdates, cancellationToken)); } private static bool TryGetPropertyValue(IVsHierarchy hierarchy, string propertyName, [NotNullWhen(returnValue: true)] out string? propertyValue) diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef index 114cb938c11bc..6ee7bd688edd0 100644 --- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef +++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef @@ -13,3 +13,8 @@ "CS"="CA719A03-D55C-48F9-85DE-D934346E7F70" "VB"="EEC3DF0D-6D3F-4544-ABF9-8E26E6A90275" +[$RootKey$\FeatureFlags\Roslyn\LSP\Completion] +"Description"="Enables the LSP-powered C#/VB completion experience to operate with prototype behavior. Note this currently will not affect local C#/VB scenarios except for C# in Razor." +"Value"=dword:00000000 +"Title"="Enable experimental C#/VB LSP completion experience" +"PreviewPaneChannels"="IntPreview,int.main" diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/MetadataToProjectReferenceConversionTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/MetadataToProjectReferenceConversionTests.vb index ab86519a9eb61..2ffab4f2801e5 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/MetadataToProjectReferenceConversionTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/MetadataToProjectReferenceConversionTests.vb @@ -313,5 +313,21 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim project2.RemoveFromWorkspace() End Using End Function + + + + Public Async Function DoNotCreateProjectReferenceWhenReferencingOwnOutput() As Task + Using environment = New TestEnvironment() + Dim project = Await environment.ProjectFactory.CreateAndAddToWorkspaceAsync("project", LanguageNames.CSharp, CancellationToken.None) + + Const ReferencePath = "C:\project.dll" + + project.OutputFilePath = ReferencePath + + project.AddMetadataReference(ReferencePath, MetadataReferenceProperties.Assembly) + + Assert.Single(environment.Workspace.CurrentSolution.Projects.Single().MetadataReferences) + End Using + End Function End Class End Namespace diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs index 7ec18f70c2686..413f0bd115e45 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs @@ -32,6 +32,7 @@ protected CodeGenerationAbstractMethodSymbol( } public abstract int Arity { get; } + public abstract System.Reflection.MethodImplAttributes MethodImplementationFlags { get; } public abstract bool ReturnsVoid { get; } public abstract bool ReturnsByRef { get; } public abstract bool ReturnsByRefReadonly { get; } diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConstructedMethodSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConstructedMethodSymbol.cs index a14b8e2fd61d4..a4687876e196e 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConstructedMethodSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationConstructedMethodSymbol.cs @@ -71,6 +71,8 @@ public override ImmutableArray Parameters public override bool IsReadOnly => _constructedFrom.IsReadOnly; public override bool IsInitOnly => _constructedFrom.IsInitOnly; + public override System.Reflection.MethodImplAttributes MethodImplementationFlags => _constructedFrom.MethodImplementationFlags; + public override IMethodSymbol OverriddenMethod => // TODO(cyrusn): Construct this. _constructedFrom.OverriddenMethod; diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs index 9d641c16dfed2..6eb2470920695 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationMethodSymbol.cs @@ -99,6 +99,8 @@ public override ImmutableArray TypeArguments public override bool IsReadOnly => Modifiers.IsReadOnly; public override bool IsInitOnly { get; } + public override System.Reflection.MethodImplAttributes MethodImplementationFlags => default; + public override IMethodSymbol OverriddenMethod => null; public override IMethodSymbol ReducedFrom => null; diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs index 084d2450d05f4..4b9026b29ed95 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs @@ -49,7 +49,8 @@ public async Task SerializeAsync(IPersistentStorageService persistentServi WriteDiagnosticData(writer, items, cancellationToken); } - using var storage = await persistentService.GetStorageAsync(project.Solution, cancellationToken).ConfigureAwait(false); + var storage = await persistentService.GetStorageAsync(project.Solution, cancellationToken).ConfigureAwait(false); + await using var _ = storage.ConfigureAwait(false); stream.Position = 0; @@ -69,7 +70,8 @@ public async ValueTask> DeserializeAsync(IPersist { Contract.ThrowIfFalse(textDocument == null || textDocument.Project == project); - using var storage = await persistentService.GetStorageAsync(project.Solution, cancellationToken).ConfigureAwait(false); + var storage = await persistentService.GetStorageAsync(project.Solution, cancellationToken).ConfigureAwait(false); + await using var _ = storage.ConfigureAwait(false); var readTask = (textDocument != null) ? textDocument is Document document ? diff --git a/src/Workspaces/Core/Portable/Differencing/Match.cs b/src/Workspaces/Core/Portable/Differencing/Match.cs index 1f4fbf8202d80..e4a8e07a730a6 100644 --- a/src/Workspaces/Core/Portable/Differencing/Match.cs +++ b/src/Workspaces/Core/Portable/Differencing/Match.cs @@ -210,18 +210,31 @@ private void ComputeMatchForLabel(List s1, List s2, int tiedToAnce // consider avoiding matching them to all other nodes of the same label. // Rather we should only match them with their siblings that share the same parent. - var ancestor1 = _comparer.GetAncestor(node1, tiedToAncestor); - var ancestor2 = _comparer.GetAncestor(node2, tiedToAncestor); - - // Since CategorizeNodesByLabels added nodes to the s1/s2 lists in depth-first prefix order, - // we can also accept equality in the following condition. That's because we find the partner - // of the parent node before we get to finding it for the child node of the same kind. - Debug.Assert(_comparer.GetLabel(ancestor1) <= _comparer.GetLabel(node1)); - - if (!Contains(ancestor1, ancestor2)) + // Check if nodes that are configured to be tied to their ancestor have the respective ancestor matching. + // In cases when we compare substrees rooted below both of these ancestors we assume the ancestors are + // matching since the roots of the subtrees must match and therefore their ancestors must match as well. + // If one node's ancestor is present in the subtree and the other isn't then we are not in the scenario + // of comparing subtrees with matching roots and thus we consider the nodes not matching. + + var hasAncestor1 = _comparer.TryGetAncestor(node1, tiedToAncestor, out var ancestor1); + var hasAncestor2 = _comparer.TryGetAncestor(node2, tiedToAncestor, out var ancestor2); + if (hasAncestor1 != hasAncestor2) { continue; } + + if (hasAncestor1) + { + // Since CategorizeNodesByLabels added nodes to the s1/s2 lists in depth-first prefix order, + // we can also accept equality in the following condition. That's because we find the partner + // of the parent node before we get to finding it for the child node of the same kind. + Debug.Assert(_comparer.GetLabel(ancestor1) <= _comparer.GetLabel(node1)); + + if (!Contains(ancestor1, ancestor2)) + { + continue; + } + } } // We know that diff --git a/src/Workspaces/Core/Portable/Differencing/TreeComparer.cs b/src/Workspaces/Core/Portable/Differencing/TreeComparer.cs index 876e78517fa5e..a0ba580f468cc 100644 --- a/src/Workspaces/Core/Portable/Differencing/TreeComparer.cs +++ b/src/Workspaces/Core/Portable/Differencing/TreeComparer.cs @@ -100,15 +100,25 @@ internal TNode GetParent(TNode node) return parent!; } - internal TNode GetAncestor(TNode node, int level) + internal bool TryGetAncestor(TNode node, int level, [MaybeNullWhen(false)] out TNode ancestor) { while (level > 0) { - node = GetParent(node); + if (TryGetParent(node, out var parent)) + { + node = parent; + } + else + { + ancestor = default; + return false; + } + level--; } - return node; + ancestor = node; + return true; } /// diff --git a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs index 38b9b88b9ad7c..d9427472ed69c 100644 --- a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs +++ b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs @@ -34,5 +34,6 @@ internal static class WellKnownExperimentNames public const string OOPServerGC = "Roslyn.OOPServerGC"; public const string ImportsOnPasteDefaultEnabled = "Roslyn.ImportsOnPasteDefaultEnabled"; public const string RemoveUnusedReferences = "Roslyn.RemoveUnusedReferences"; + public const string LSPCompletion = "Roslyn.LSP.Completion"; } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 5ac5b885504da..d914bc68fd48a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -29,6 +29,13 @@ internal partial class FindReferencesSearchEngine private readonly CancellationToken _cancellationToken; private readonly FindReferencesSearchOptions _options; + /// + /// Scheduler to run our tasks on. If we're in mode, we'll + /// run all our tasks concurrently. Otherwise, we will run them serially using + /// + private readonly TaskScheduler _scheduler; + private static readonly TaskScheduler s_exclusiveScheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler; + public FindReferencesSearchEngine( Solution solution, IImmutableSet? documents, @@ -45,6 +52,12 @@ public FindReferencesSearchEngine( _options = options; _progressTracker = progress.ProgressTracker; + + // If we're an explicit invocation, just defer to the threadpool to execute all our work in parallel to get + // things done as quickly as possible. If we're running implicitly, then use a + // ConcurrentExclusiveSchedulerPair's exclusive scheduler as that's the most built-in way in the TPL to get + // will run things serially. + _scheduler = _options.Explicit ? TaskScheduler.Default : s_exclusiveScheduler; } public async Task FindReferencesAsync(ISymbol symbol) @@ -87,7 +100,7 @@ private async Task ProcessAsync(ProjectToDocumentMap projectToDocumentMap) using var _ = ArrayBuilder.GetInstance(out var tasks); foreach (var (project, documentMap) in projectToDocumentMap) - tasks.Add(Task.Run(() => ProcessProjectAsync(project, documentMap), _cancellationToken)); + tasks.Add(Task.Factory.StartNew(() => ProcessProjectAsync(project, documentMap), _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); await Task.WhenAll(tasks).ConfigureAwait(false); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs index a59c78e7eb398..81c18d64475d3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs @@ -34,8 +34,8 @@ private async Task CreateProjectToDocumentMapAsync(Project { foreach (var (symbol, finder) in projectQueue) { - tasks.Add(Task.Run(() => - DetermineDocumentsToSearchAsync(project, symbol, finder), _cancellationToken)); + tasks.Add(Task.Factory.StartNew(() => + DetermineDocumentsToSearchAsync(project, symbol, finder), _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); } } @@ -139,7 +139,7 @@ private async Task DetermineAllSymbolsCoreAsync( using var _ = ArrayBuilder.GetInstance(out var finderTasks); foreach (var f in _finders) { - finderTasks.Add(Task.Run(async () => + finderTasks.Add(Task.Factory.StartNew(async () => { using var _ = ArrayBuilder.GetInstance(out var symbolTasks); @@ -159,7 +159,7 @@ private async Task DetermineAllSymbolsCoreAsync( _cancellationToken.ThrowIfCancellationRequested(); await Task.WhenAll(symbolTasks).ConfigureAwait(false); - }, _cancellationToken)); + }, _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); } await Task.WhenAll(finderTasks).ConfigureAwait(false); @@ -177,7 +177,8 @@ private void AddSymbolTasks( { Contract.ThrowIfNull(child); _cancellationToken.ThrowIfCancellationRequested(); - symbolTasks.Add(Task.Run(() => DetermineAllSymbolsCoreAsync(child, result), _cancellationToken)); + symbolTasks.Add(Task.Factory.StartNew( + () => DetermineAllSymbolsCoreAsync(child, result), _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs index aa7c49aae6f30..e35744ee0a02d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs @@ -30,7 +30,7 @@ private async Task ProcessProjectAsync( foreach (var (document, documentQueue) in documentMap) { if (document.Project == project) - documentTasks.Add(Task.Run(() => ProcessDocumentQueueAsync(document, documentQueue), _cancellationToken)); + documentTasks.Add(Task.Factory.StartNew(() => ProcessDocumentQueueAsync(document, documentQueue), _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); } await Task.WhenAll(documentTasks).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchOptions.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchOptions.cs index eccb2f2421ad5..0581091aa9f4d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchOptions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchOptions.cs @@ -2,8 +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. -#nullable disable - using System.Runtime.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -15,7 +13,8 @@ internal sealed class FindReferencesSearchOptions public static readonly FindReferencesSearchOptions Default = new( associatePropertyReferencesWithSpecificAccessor: false, - cascade: true); + cascade: true, + @explicit: true); /// /// When searching for property, associate specific references we find to the relevant @@ -39,19 +38,45 @@ internal sealed class FindReferencesSearchOptions [DataMember(Order = 1)] public bool Cascade { get; } + /// + /// Whether or not this find ref operation was explicitly invoked or not. If explicit invoked, the find + /// references operation may use more resources to get the results faster. + /// + /// + /// Features that run automatically should consider setting this to to avoid + /// unnecessarily impacting the user while they are doing other work. + /// + [DataMember(Order = 2)] + public bool Explicit { get; } + public FindReferencesSearchOptions( bool associatePropertyReferencesWithSpecificAccessor, - bool cascade) + bool cascade, + bool @explicit) { AssociatePropertyReferencesWithSpecificAccessor = associatePropertyReferencesWithSpecificAccessor; Cascade = cascade; + Explicit = @explicit; } - public FindReferencesSearchOptions WithAssociatePropertyReferencesWithSpecificAccessor(bool associatePropertyReferencesWithSpecificAccessor) - => new(associatePropertyReferencesWithSpecificAccessor, Cascade); + public FindReferencesSearchOptions With( + Optional associatePropertyReferencesWithSpecificAccessor = default, + Optional cascade = default, + Optional @explicit = default) + { + var newAssociatePropertyReferencesWithSpecificAccessor = associatePropertyReferencesWithSpecificAccessor.HasValue ? associatePropertyReferencesWithSpecificAccessor.Value : AssociatePropertyReferencesWithSpecificAccessor; + var newCascade = cascade.HasValue ? cascade.Value : Cascade; + var newExplicit = @explicit.HasValue ? @explicit.Value : Explicit; + + if (newAssociatePropertyReferencesWithSpecificAccessor == AssociatePropertyReferencesWithSpecificAccessor && + newCascade == Cascade && + newExplicit == Explicit) + { + return this; + } - public FindReferencesSearchOptions WithCascade(bool cascade) - => new(AssociatePropertyReferencesWithSpecificAccessor, cascade); + return new FindReferencesSearchOptions(newAssociatePropertyReferencesWithSpecificAccessor, newCascade, newExplicit); + } /// /// For IDE features, if the user starts searching on an accessor, then we want to give @@ -59,6 +84,6 @@ public FindReferencesSearchOptions WithCascade(bool cascade) /// then associate everything with the property. /// public static FindReferencesSearchOptions GetFeatureOptionsForStartingSymbol(ISymbol symbol) - => Default.WithAssociatePropertyReferencesWithSpecificAccessor(symbol.IsPropertyAccessor()); + => Default.With(associatePropertyReferencesWithSpecificAccessor: symbol.IsPropertyAccessor()); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs index ef8a5938d71b4..d8ee036faf9bd 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/AbstractReferenceFinder.cs @@ -276,7 +276,7 @@ protected static async Task> GetIdentifierOrGlobalNa { return (matched: true, CandidateReason.None); } - else if (await symbolInfoToMatch.CandidateSymbols.AnyAsync(s => SymbolFinder.OriginalSymbolsMatchAsync(solution, searchSymbol, s, cancellationToken)).ConfigureAwait(false)) + else if (await symbolInfoToMatch.CandidateSymbols.AnyAsync(static (s, arg) => SymbolFinder.OriginalSymbolsMatchAsync(arg.solution, arg.searchSymbol, s, arg.cancellationToken), (solution, searchSymbol, cancellationToken)).ConfigureAwait(false)) { return (matched: true, symbolInfoToMatch.CandidateReason); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs index 2831f558a81a0..a88717d9f8c3e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/PropertyAccessorSymbolReferenceFinder.cs @@ -56,7 +56,7 @@ protected override async Task> DetermineDocumentsToSear // defer to the Property finder to find these docs and combine them with the result. var propertyDocuments = await ReferenceFinders.Property.DetermineDocumentsToSearchAsync( property, project, documents, - options.WithAssociatePropertyReferencesWithSpecificAccessor(false), + options.With(associatePropertyReferencesWithSpecificAccessor: false), cancellationToken).ConfigureAwait(false); result = result.AddRange(propertyDocuments); @@ -77,7 +77,7 @@ protected override async ValueTask> FindReference { var propertyReferences = await ReferenceFinders.Property.FindReferencesInDocumentAsync( property, document, semanticModel, - options.WithAssociatePropertyReferencesWithSpecificAccessor(false), + options.With(associatePropertyReferencesWithSpecificAccessor: false), cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetRequiredLanguageService(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Legacy.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Legacy.cs index 8389c3d45c788..72dc8dc1151da 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Legacy.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindReferences_Legacy.cs @@ -82,7 +82,7 @@ public static async Task> FindReferencesAsync( FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); } - private static async Task> FindReferencesAsync( + internal static async Task> FindReferencesAsync( ISymbol symbol, Solution solution, IFindReferencesProgress progress, diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs index 223510a62964a..ef5123ffb4935 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs @@ -54,8 +54,8 @@ internal static async Task OriginalSymbolsMatchAsync( var namespace2Count = namespace2.ConstituentNamespaces.Length; if (namespace1Count != namespace2Count) { - if ((namespace1Count > 1 && await namespace1.ConstituentNamespaces.AnyAsync(n => NamespaceSymbolsMatchAsync(solution, n, namespace2, cancellationToken)).ConfigureAwait(false)) || - (namespace2Count > 1 && await namespace2.ConstituentNamespaces.AnyAsync(n2 => NamespaceSymbolsMatchAsync(solution, namespace1, n2, cancellationToken)).ConfigureAwait(false))) + if ((namespace1Count > 1 && await namespace1.ConstituentNamespaces.AnyAsync(static (n, arg) => NamespaceSymbolsMatchAsync(arg.solution, n, arg.namespace2, arg.cancellationToken), (solution, namespace2, cancellationToken)).ConfigureAwait(false)) || + (namespace2Count > 1 && await namespace2.ConstituentNamespaces.AnyAsync(static (n2, arg) => NamespaceSymbolsMatchAsync(arg.solution, arg.namespace1, n2, arg.cancellationToken), (solution, namespace1, cancellationToken)).ConfigureAwait(false))) { return true; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs index 197b4473abff6..08f90282d197d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Serialization.cs @@ -70,55 +70,54 @@ private static async Task TryLoadOrCreateAsync( // Ok, we can use persistence. First try to load from the persistence service. var persistentStorageService = (IChecksummedPersistentStorageService)solution.Workspace.Services.GetService(); - T result; - using (var storage = await persistentStorageService.GetStorageAsync(solution, checkBranchId: false, cancellationToken).ConfigureAwait(false)) + var storage = await persistentStorageService.GetStorageAsync(solution, checkBranchId: false, cancellationToken).ConfigureAwait(false); + await using var _ = storage.ConfigureAwait(false); + + // Get the unique key to identify our data. + var key = PrefixMetadataSymbolTreeInfo + keySuffix; + using (var stream = await storage.ReadStreamAsync(key, checksum, cancellationToken).ConfigureAwait(false)) + using (var reader = ObjectReader.TryGetReader(stream, cancellationToken: cancellationToken)) { - // Get the unique key to identify our data. - var key = PrefixMetadataSymbolTreeInfo + keySuffix; - using (var stream = await storage.ReadStreamAsync(key, checksum, cancellationToken).ConfigureAwait(false)) - using (var reader = ObjectReader.TryGetReader(stream, cancellationToken: cancellationToken)) + if (reader != null) { - if (reader != null) + // We have some previously persisted data. Attempt to read it back. + // If we're able to, and the version of the persisted data matches + // our version, then we can reuse this instance. + var read = tryReadObject(reader); + if (read != null) { - // We have some previously persisted data. Attempt to read it back. - // If we're able to, and the version of the persisted data matches - // our version, then we can reuse this instance. - result = tryReadObject(reader); - if (result != null) - { - // If we were able to read something in, it's checksum better - // have matched the checksum we expected. - Debug.Assert(result.Checksum == checksum); - return result; - } + // If we were able to read something in, it's checksum better + // have matched the checksum we expected. + Debug.Assert(read.Checksum == checksum); + return read; } } + } - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - // Couldn't read from the persistence service. If we've been asked to only load - // data and not create new instances in their absence, then there's nothing left - // to do at this point. - if (loadOnly) - { - return null; - } + // Couldn't read from the persistence service. If we've been asked to only load + // data and not create new instances in their absence, then there's nothing left + // to do at this point. + if (loadOnly) + { + return null; + } - // Now, try to create a new instance and write it to the persistence service. - result = await CreateWithLoggingAsync().ConfigureAwait(false); - Contract.ThrowIfNull(result); + // Now, try to create a new instance and write it to the persistence service. + var result = await CreateWithLoggingAsync().ConfigureAwait(false); + Contract.ThrowIfNull(result); - using (var stream = SerializableBytes.CreateWritableStream()) + using (var stream = SerializableBytes.CreateWritableStream()) + { + using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) { - using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) - { - result.WriteTo(writer); - } + result.WriteTo(writer); + } - stream.Position = 0; + stream.Position = 0; - await storage.WriteStreamAsync(key, stream, checksum, cancellationToken).ConfigureAwait(false); - } + await storage.WriteStreamAsync(key, stream, checksum, cancellationToken).ConfigureAwait(false); } return result; diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs index 73e570290fb4b..518e4a3cdeb7f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Persistence.cs @@ -28,7 +28,8 @@ internal sealed partial class SyntaxTreeIndex : IObjectWritable try { // attempt to load from persisted state - using var storage = await persistentStorageService.GetStorageAsync(solution, checkBranchId: false, cancellationToken).ConfigureAwait(false); + var storage = await persistentStorageService.GetStorageAsync(solution, checkBranchId: false, cancellationToken).ConfigureAwait(false); + await using var _ = storage.ConfigureAwait(false); using var stream = await storage.ReadStreamAsync(document, PersistenceName, checksum, cancellationToken).ConfigureAwait(false); using var reader = ObjectReader.TryGetReader(stream, cancellationToken: cancellationToken); if (reader != null) @@ -71,7 +72,8 @@ private async Task SaveAsync( try { - using var storage = await persistentStorageService.GetStorageAsync(solution, checkBranchId: false, cancellationToken).ConfigureAwait(false); + var storage = await persistentStorageService.GetStorageAsync(solution, checkBranchId: false, cancellationToken).ConfigureAwait(false); + await using var _ = storage.ConfigureAwait(false); using var stream = SerializableBytes.CreateWritableStream(); using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) @@ -99,7 +101,8 @@ private static async Task PrecalculatedAsync( // check whether we already have info for this document try { - using var storage = await persistentStorageService.GetStorageAsync(solution, checkBranchId: false, cancellationToken).ConfigureAwait(false); + var storage = await persistentStorageService.GetStorageAsync(solution, checkBranchId: false, cancellationToken).ConfigureAwait(false); + await using var _ = storage.ConfigureAwait(false); // Check if we've already stored a checksum and it matches the checksum we // expect. If so, we're already precalculated and don't have to recompute // this index. Otherwise if we don't have a checksum, or the checksums don't diff --git a/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs b/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs index b91d57fe0e641..21743014759f0 100644 --- a/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs +++ b/src/Workspaces/Core/Portable/Storage/AbstractPersistentStorageService.cs @@ -234,6 +234,9 @@ public static IChecksummedPersistentStorage AddReferenceCountToAndCreateWrapper( public void Dispose() => _storage.Dispose(); + public ValueTask DisposeAsync() + => _storage.DisposeAsync(); + public Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken) => _storage.Target.ChecksumMatchesAsync(name, checksum, cancellationToken); @@ -249,13 +252,13 @@ public Task ChecksumMatchesAsync(ProjectKey project, string name, Checksum public Task ChecksumMatchesAsync(DocumentKey document, string name, Checksum checksum, CancellationToken cancellationToken) => _storage.Target.ChecksumMatchesAsync(document, name, checksum, cancellationToken); - public Task ReadStreamAsync(string name, CancellationToken cancellationToken) + public Task ReadStreamAsync(string name, CancellationToken cancellationToken) => _storage.Target.ReadStreamAsync(name, cancellationToken); - public Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken) + public Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken) => _storage.Target.ReadStreamAsync(project, name, cancellationToken); - public Task ReadStreamAsync(Document document, string name, CancellationToken cancellationToken) + public Task ReadStreamAsync(Document document, string name, CancellationToken cancellationToken) => _storage.Target.ReadStreamAsync(document, name, cancellationToken); public Task ReadStreamAsync(string name, Checksum checksum, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs index 85c0e7dbef7e8..418b29c4b8240 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage.cs @@ -88,6 +88,12 @@ private SQLitePersistentStorage( } public override void Dispose() + { + var task = DisposeAsync().AsTask(); + task.Wait(); + } + + public override async ValueTask DisposeAsync() { try { @@ -95,7 +101,7 @@ public override void Dispose() // persisted to the DB. try { - FlushWritesOnClose(); + await FlushWritesOnCloseAsync().ConfigureAwait(false); } catch (Exception e) { diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs index 58f3a7ee276e3..7bb665bd52a99 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_FlushWrites.cs @@ -31,13 +31,13 @@ private Task FlushInMemoryDataToDiskIfNotShutdownAsync(CancellationToken cancell return PerformWriteAsync(FlushInMemoryDataToDisk, cancellationToken); } - private void FlushWritesOnClose() + private Task FlushWritesOnCloseAsync() { // Issue a write task to write this all out to disk. // // Note: this only happens on close, so we don't try to avoid allocations here. - var writeTask = PerformWriteAsync( + return PerformWriteAsync( () => { // Perform the actual write while having exclusive access to the scheduler. @@ -59,9 +59,6 @@ private void FlushWritesOnClose() // read/write after releasing us. _shutdownTokenSource.Cancel(); }, CancellationToken.None); - - // Wait for that task to finish. - writeTask.Wait(); } private void FlushInMemoryDataToDisk() diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs index 4fc5a526d715e..8fe74c72239b2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/AbstractPersistentStorage.cs @@ -35,6 +35,7 @@ protected AbstractPersistentStorage( } public abstract void Dispose(); + public abstract ValueTask DisposeAsync(); public abstract Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken); public abstract Task ReadStreamAsync(string name, Checksum? checksum, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs index 0c33811f395a5..c15c8d1601937 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/IPersistentStorage.cs @@ -2,8 +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. -#nullable disable - using System; using System.IO; using System.Threading; @@ -11,12 +9,16 @@ namespace Microsoft.CodeAnalysis.Host { - public interface IPersistentStorage : IDisposable + /// + /// Instances of support both synchronous and asynchronous disposal. Asynchronous + /// disposal should always be preferred as the implementation of synchronous disposal may end up blocking the caller + /// on async work. + /// + public interface IPersistentStorage : IDisposable, IAsyncDisposable { - Task ReadStreamAsync(string name, CancellationToken cancellationToken = default); - - Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken = default); - Task ReadStreamAsync(Document document, string name, CancellationToken cancellationToken = default); + Task ReadStreamAsync(string name, CancellationToken cancellationToken = default); + Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken = default); + Task ReadStreamAsync(Document document, string name, CancellationToken cancellationToken = default); Task WriteStreamAsync(string name, Stream stream, CancellationToken cancellationToken = default); Task WriteStreamAsync(Project project, string name, Stream stream, CancellationToken cancellationToken = default); diff --git a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs index 030716cb1406c..d2bf20ad513a6 100644 --- a/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs +++ b/src/Workspaces/Core/Portable/Workspace/Host/PersistentStorage/NoOpPersistentStorage.cs @@ -22,6 +22,11 @@ public void Dispose() { } + public ValueTask DisposeAsync() + { + return ValueTaskFactory.CompletedTask; + } + public Task ChecksumMatchesAsync(string name, Checksum checksum, CancellationToken cancellationToken) => SpecializedTasks.False; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index b8cff263c5f91..56cf3059ea3f2 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -1871,9 +1871,17 @@ private void CheckCircularProjectReferences(ProjectId projectId, IReadOnlyCollec { foreach (var projectReference in projectReferences) { - if (projectId == projectReference.ProjectId || _state.ContainsTransitiveReference(projectReference.ProjectId, projectId)) + if (projectId == projectReference.ProjectId) { - throw new InvalidOperationException(WorkspacesResources.The_project_already_transitively_references_the_target_project); + throw new InvalidOperationException(WorkspacesResources.A_project_may_not_reference_itself); + } + + if (_state.ContainsTransitiveReference(projectReference.ProjectId, projectId)) + { + throw new InvalidOperationException( + string.Format(WorkspacesResources.Adding_project_reference_from_0_to_1_will_cause_a_circular_reference, + projectId, + projectReference.ProjectId)); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs b/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs index 2c18b2b54f6a4..5b017c4bdf750 100644 --- a/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs +++ b/src/Workspaces/Core/Portable/Workspace/TextExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Threading; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Text @@ -92,5 +93,18 @@ public static ImmutableArray GetRelatedDocuments(this SourceTextContai return null; } + + /// + /// Tries to get the document corresponding to the text from the current partial solution + /// associated with the text's container. If the document does not contain the exact text a document + /// from a new solution containing the specified text is constructed. If no document is associated + /// with the specified text's container, or the text's container isn't associated with a workspace, + /// then the method returns false. + /// + internal static Document? GetDocumentWithFrozenPartialSemantics(this SourceText text, CancellationToken cancellationToken) + { + var document = text.GetOpenDocumentInCurrentContextWithChanges(); + return document?.WithFrozenPartialSemantics(cancellationToken); + } } } diff --git a/src/Workspaces/Core/Portable/WorkspacesResources.resx b/src/Workspaces/Core/Portable/WorkspacesResources.resx index eeb6d7a224261..00bc428ca8a14 100644 --- a/src/Workspaces/Core/Portable/WorkspacesResources.resx +++ b/src/Workspaces/Core/Portable/WorkspacesResources.resx @@ -222,8 +222,8 @@ The project already contains the specified reference. - - The project already transitively references the target project. + + A project may not reference itself. The solution already contains the specified document. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf index adab2e4471887..4fc8f4a21d19c 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. Přidávání dokumentů konfigurace analyzátoru se nepodporuje. @@ -312,11 +317,6 @@ Projekt už odkazuje na cílový projekt. - - The project already transitively references the target project. - Projekt už tranzitivně odkazuje na cílový projekt. - - The solution already contains the specified document. Řešení už obsahuje zadaný dokument. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf index d1ec5f908df45..72e0005f07a29 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. Das Hinzufügen von Konfigurationsdokumenten des Analysetools wird nicht unterstützt. @@ -312,11 +317,6 @@ Das Projekt verweist bereits auf das Zielprojekt. - - The project already transitively references the target project. - Das Projekt verweist bereits transitiv auf das Zielprojekt. - - The solution already contains the specified document. Die Lösung enthält bereits das angegebene Dokument. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf index ea9bf38f3c524..a2801fa916b8a 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. No se permite agregar documentos de configuración del analizador. @@ -312,11 +317,6 @@ El proyecto ya hace referencia al proyecto de destino. - - The project already transitively references the target project. - El proyecto ya hace referencia de forma transitiva al proyecto de destino. - - The solution already contains the specified document. La solución ya contiene el documento especificado. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf index 029668672d5d0..78662bc24181b 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. L'ajout de documents de configuration de l'analyseur n'est pas pris en charge. @@ -312,11 +317,6 @@ Le projet référence déjà le projet cible. - - The project already transitively references the target project. - Le projet référence déjà transitivement le projet cible. - - The solution already contains the specified document. La solution contient déjà le document spécifié. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf index 9f957fb894658..71ed24579c257 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. L'aggiunta di documenti di configurazione dell'analizzatore non è supportata. @@ -312,11 +317,6 @@ Il progetto fa già riferimento al progetto di destinazione. - - The project already transitively references the target project. - Il progetto fa già riferimento in modo transitivo al progetto di destinazione. - - The solution already contains the specified document. La soluzione contiene già il documento specificato. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf index 191e0e88f1f04..bd554d3c9c74b 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. アナライザー構成ドキュメントの追加はサポートされていません。 @@ -312,11 +317,6 @@ プロジェクトは、既に対象のプロジェクトを参照します。 - - The project already transitively references the target project. - プロジェクトは、既に推移的に対象のプロジェクトを参照しています。 - - The solution already contains the specified document. ソリューションには、指定されたドキュメントが既に含まれています。 diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf index bc4cb67a5c98b..4a85c161af349 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. 분석기 구성 문서 추가는 지원되지 않습니다. @@ -312,11 +317,6 @@ 프로젝트가 대상 프로젝트를 이미 참조하고 있습니다. - - The project already transitively references the target project. - 프로젝트가 대상 프로젝트를 이미 타동적으로 참조하고 있습니다. - - The solution already contains the specified document. 솔루션에 지정한 문서가 이미 포함되어 있습니다. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf index 29eedc13e9a6c..7e754b93a2021 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. Dodawanie dokumentów z konfiguracją analizatora nie jest obsługiwane. @@ -312,11 +317,6 @@ Projekt już przywołuje projekt docelowy. - - The project already transitively references the target project. - Już istnieje odwołanie przechodnie projektu do projektu docelowego. - - The solution already contains the specified document. Rozwiązanie już zawiera określony dokument. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf index b5490eb610773..9d4c30ed6c513 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. Não há suporte para a adição de documentos da configuração do analisador. @@ -312,11 +317,6 @@ O projeto já referencia o projeto de destino. - - The project already transitively references the target project. - O projeto já referencia transitivamente o projeto de destino. - - The solution already contains the specified document. A solução já contém o documento especificado. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf index 049eb9fcd810e..b44bf058525f8 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. Добавление документов конфигурации анализатора не поддерживается. @@ -312,11 +317,6 @@ Проект уже ссылается на целевой проект. - - The project already transitively references the target project. - Проект уже транзитивно ссылается на целевой проект. - - The solution already contains the specified document. Указанный документ уже находится в решении. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf index 5f613044f565e..4a5b895abda56 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. Çözümleyici yapılandırma belgelerinin eklenmesi desteklenmiyor. @@ -312,11 +317,6 @@ Proje zaten hedef projeye başvuruyor. - - The project already transitively references the target project. - Proje zaten hedef projeye geçişli olarak başvuruyor. - - The solution already contains the specified document. Çözüm belirtilen belgeyi zaten içeriyor. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf index 1edc403adca2a..fb282c3ed7c8d 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. 不支持添加分析器配置文档。 @@ -312,11 +317,6 @@ 项目已引用目标项目。 - - The project already transitively references the target project. - 项目已通过可传递的方式引用目标项目。 - - The solution already contains the specified document. 解决方案已包含指定的文档。 diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf index a84392da956af..4fcffc99d9f72 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf @@ -2,6 +2,11 @@ + + A project may not reference itself. + A project may not reference itself. + + Adding analyzer config documents is not supported. 不支援新增分析器組態文件。 @@ -312,11 +317,6 @@ 此專案已經參考目標專案。 - - The project already transitively references the target project. - 此專案已經以可轉移方式參考目標專案。 - - The solution already contains the specified document. 此方案已含有指定的文件。 diff --git a/src/Workspaces/CoreTest/Collections/SegmentedArrayHelperTests.cs b/src/Workspaces/CoreTest/Collections/SegmentedArrayHelperTests.cs index e14323e0527f7..e6a06f9ad5d59 100644 --- a/src/Workspaces/CoreTest/Collections/SegmentedArrayHelperTests.cs +++ b/src/Workspaces/CoreTest/Collections/SegmentedArrayHelperTests.cs @@ -44,7 +44,7 @@ public void CalculateSegmentSize(int elementSize) _ => throw ExceptionUtilities.Unreachable, }; - Assert.Equal(expected, SegmentedArrayHelper.CalculateSegmentSize(elementSize)); + Assert.Equal(expected, SegmentedArrayHelper.TestAccessor.CalculateSegmentSize(elementSize)); } [Theory] @@ -69,7 +69,7 @@ public void CalculateSegmentShift(int segmentSize) _ => throw ExceptionUtilities.Unreachable, }; - Assert.Equal(expected, SegmentedArrayHelper.CalculateSegmentShift(segmentSize)); + Assert.Equal(expected, SegmentedArrayHelper.TestAccessor.CalculateSegmentShift(segmentSize)); } [Theory] @@ -94,7 +94,7 @@ public void CalculateOffsetMask(int segmentSize) _ => throw ExceptionUtilities.Unreachable, }; - Assert.Equal(expected, SegmentedArrayHelper.CalculateOffsetMask(segmentSize)); + Assert.Equal(expected, SegmentedArrayHelper.TestAccessor.CalculateOffsetMask(segmentSize)); } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/Host/ProcessExtensions.cs b/src/Workspaces/Remote/ServiceHub/Services/Host/ProcessExtensions.cs new file mode 100644 index 0000000000000..668b817fdae13 --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/Host/ProcessExtensions.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.ComponentModel; +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.Remote +{ + internal static class ProcessExtensions + { + private static bool s_settingPrioritySupported = true; + + public static bool TrySetPriorityClass(this Process process, ProcessPriorityClass priorityClass) + { + if (!s_settingPrioritySupported) + { + return false; + } + + try + { + process.PriorityClass = priorityClass; + return true; + } + catch (Exception e) when (e is PlatformNotSupportedException or Win32Exception) + { + // the runtime does not support changing process priority + s_settingPrioritySupported = false; + + return false; + } + } + } +} diff --git a/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.cs b/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.cs index e6e6b35adaa96..7ae4d366c386a 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.cs @@ -10,6 +10,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Runtime; using System.Runtime.InteropServices; using System.Text; using System.Threading; @@ -51,6 +52,14 @@ internal partial class RemoteHostService : ServiceBase, IRemoteHostService, IAss static RemoteHostService() { + if (GCSettings.IsServerGC) + { + // Server GC runs processor-affinitized threads with high priority. To avoid interfering with other + // applications while still allowing efficient out-of-process execution, slightly reduce the process + // priority when using server GC. + Process.GetCurrentProcess().TrySetPriorityClass(ProcessPriorityClass.BelowNormal); + } + // this is the very first service which will be called from client (VS) // we set up logger here RoslynLogger.SetLogger(new EtwLogger(s_logChecker)); diff --git a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs index 55d905ebd223d..8ceebb90a08b1 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SemanticClassificationCache/RemoteSemanticClassificationCacheService.cs @@ -94,7 +94,8 @@ private static async Task CacheSemanticClassificationsAsync(Document document, C if (persistenceService == null) return; - using var storage = await persistenceService.GetStorageAsync(solution, cancellationToken).ConfigureAwait(false); + var storage = await persistenceService.GetStorageAsync(solution, cancellationToken).ConfigureAwait(false); + await using var _ = storage.ConfigureAwait(false); if (storage == null) return; @@ -264,7 +265,8 @@ private async Task> TryReadCachedSemanticClassifi if (persistenceService == null) return default; - using var storage = await persistenceService.GetStorageAsync(workspace, documentKey.Project.Solution, checkBranchId: false, cancellationToken).ConfigureAwait(false); + var storage = await persistenceService.GetStorageAsync(workspace, documentKey.Project.Solution, checkBranchId: false, cancellationToken).ConfigureAwait(false); + await using var _ = storage.ConfigureAwait(false); if (storage == null) return default; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.cs index 2199207f358af..91ae8158a0d30 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Context/FormattingContext.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Shared.Collections; @@ -40,7 +41,7 @@ internal partial class FormattingContext // anchor token to anchor data map. // unlike anchorTree that would return anchor data for given span in the tree, it will return // anchorData based on key which is anchor token. - private readonly Dictionary _anchorBaseTokenMap; + private readonly SegmentedDictionary _anchorBaseTokenMap; // hashset to prevent duplicate entries in the trees. private readonly HashSet _indentationMap; @@ -69,7 +70,7 @@ public FormattingContext(AbstractFormatEngine engine, TokenStream tokenStream) _suppressFormattingTree = new ContextIntervalTree(new SuppressIntervalIntrospector()); _anchorTree = new ContextIntervalTree(new FormattingContextIntervalIntrospector()); - _anchorBaseTokenMap = new Dictionary(); + _anchorBaseTokenMap = new SegmentedDictionary(); _indentationMap = new HashSet(); _suppressWrappingMap = new HashSet(); @@ -272,7 +273,7 @@ public void AddSuppressOperations( List operations, CancellationToken cancellationToken) { - var valuePairs = new (SuppressOperation operation, bool shouldSuppress, bool onSameLine)[operations.Count]; + var valuePairs = new SegmentedArray<(SuppressOperation operation, bool shouldSuppress, bool onSameLine)>(operations.Count); // TODO: think about a way to figure out whether it is already suppressed and skip the expensive check below. for (var i = 0; i < operations.Count; i++) @@ -294,15 +295,15 @@ public void AddSuppressOperations( valuePairs[i] = (operation, shouldSuppress: true, onSameLine); } - valuePairs.Do(v => + foreach (var (operation, shouldSuppress, onSameLine) in valuePairs) { cancellationToken.ThrowIfCancellationRequested(); - if (v.shouldSuppress) + if (shouldSuppress) { - AddSuppressOperation(v.operation, v.onSameLine); + AddSuppressOperation(operation, onSameLine); } - }); + } } private void AddSuppressOperation(SuppressOperation operation, bool onSameLine) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.cs index accd062b93db2..aae4c0b9c956e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/Engine/TokenStream.cs @@ -30,7 +30,7 @@ internal partial class TokenStream private readonly SegmentedList _tokens; // caches original trivia info to improve perf - private readonly TriviaData[] _cachedOriginalTriviaInfo; + private readonly SegmentedArray _cachedOriginalTriviaInfo; // formatting engine can be used either with syntax tree or without // this will reconstruct information that reside in syntax tree from root node @@ -65,7 +65,7 @@ public TokenStream(TreeData treeData, AnalyzerConfigOptions options, TextSpan sp Debug.Assert(this.TokenCount > 0); // initialize trivia related info - _cachedOriginalTriviaInfo = new TriviaData[this.TokenCount - 1]; + _cachedOriginalTriviaInfo = new SegmentedArray(this.TokenCount - 1); // Func Cache _getTriviaData = this.GetTriviaData; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledHashSet.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledHashSet.cs index 5bce6f2cb684b..be5b7d56e52ea 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledHashSet.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/ObjectPools/PooledHashSet.cs @@ -2,9 +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 Roslyn.Utilities; + namespace Microsoft.CodeAnalysis.PooledObjects { - internal partial class PooledHashSet : IPooled + internal partial class PooledHashSet : IPooled, IReadOnlySet { public static PooledDisposer> GetInstance(out PooledHashSet instance) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IReferenceCountedDisposable.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IReferenceCountedDisposable.cs index 953989d8e950b..16c85b3680415 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IReferenceCountedDisposable.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IReferenceCountedDisposable.cs @@ -13,6 +13,9 @@ namespace Roslyn.Utilities /// /// internal interface IReferenceCountedDisposable : IDisposable +#if !CODE_STYLE + , IAsyncDisposable +#endif where T : class { /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ReferenceCountedDisposable.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ReferenceCountedDisposable.cs index 3f950b1f7484f..ccc27196b760c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ReferenceCountedDisposable.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ReferenceCountedDisposable.cs @@ -4,6 +4,8 @@ using System; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; namespace Roslyn.Utilities { @@ -173,6 +175,27 @@ private ReferenceCountedDisposable(T instance, StrongBox referenceCount) /// use. /// public void Dispose() + { + var instanceToDispose = DisposeImpl(); + instanceToDispose?.Dispose(); + } + + public ValueTask DisposeAsync() + { + var instanceToDispose = DisposeImpl(); + if (instanceToDispose == null) + return ValueTaskFactory.CompletedTask; + +#if !CODE_STYLE + if (instanceToDispose is IAsyncDisposable asyncDisposable) + return asyncDisposable.DisposeAsync(); +#endif + + instanceToDispose.Dispose(); + return ValueTaskFactory.CompletedTask; + } + + private T? DisposeImpl() { T? instanceToDispose = null; lock (_boxedReferenceCount) @@ -180,7 +203,7 @@ public void Dispose() if (_instance == null) { // Already disposed; allow multiple without error. - return; + return null; } _boxedReferenceCount.Value--; @@ -193,7 +216,7 @@ public void Dispose() _instance = null; } - instanceToDispose?.Dispose(); + return instanceToDispose; } /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ReferenceCountedDisposableCache.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ReferenceCountedDisposableCache.cs index 87fcdf4704255..cd86b1d66a3e5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ReferenceCountedDisposableCache.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ReferenceCountedDisposableCache.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace Roslyn.Utilities { @@ -15,8 +16,7 @@ namespace Roslyn.Utilities internal sealed class ReferenceCountedDisposableCache where TValue : class, IDisposable where TKey : notnull { - private readonly Dictionary.WeakReference> _cache = - new(); + private readonly Dictionary.WeakReference> _cache = new(); private readonly object _gate = new(); public IReferenceCountedDisposable> GetOrCreate(TKey key, Func valueCreator, TArg arg)